home *** CD-ROM | disk | FTP | other *** search
/ Microsoft Programmer's Library 1.3 / Microsoft_Programmers_Library.7z / MPL / os2 / os2adv.txt < prev    next >
Encoding:
Text File  |  2013-11-08  |  1.7 MB  |  38,599 lines

Text Truncated. Only the first 1MB is shown below. Download the file for the complete contents.
  1.  Advanced OS/2 Programming
  2.  
  3.  
  4.  ════════════════════════════════════════════════════════════════════════════
  5.  
  6.  Advanced OS/2 Programming
  7.  
  8.  The Microsoft(R) Guide to the OS/2 Kernel
  9.  For Assembly Language and C Programmers
  10.  
  11.  By Ray Duncan
  12.  
  13.  ════════════════════════════════════════════════════════════════════════════
  14.  
  15.  PUBLISHED BY
  16.  Microsoft Press
  17.  A Division of Microsoft Corporation
  18.  16011 NE 36th Way, Box 97017, Redmond, Washington 98073-9717
  19.  Copyright (C) 1989 by Ray Duncan
  20.  All rights reserved. No part of the contents of this book may
  21.  be reproduced or transmitted in any form or by any means without
  22.  the written permission of the publisher.
  23.  
  24.  Library of Congress Cataloging in Publication Data
  25.  
  26.  Duncan, Ray, 1952-
  27.  Advanced OS/2 programming: the Microsoft guide to the OS/2 kernel for
  28.  assembly language and C programmers / Ray Duncan
  29.            p.          cm.
  30.  Includes index.
  31.  1. OS/2 (Computer operating system)  2. Assembler language
  32.  (Computer program language)      I. Title.
  33.  QA76.76.063D8585      1989      88-21103
  34.  005.4'469──dc19                    CIP
  35.  ISBN 1-55615-045-8
  36.  Printed and bound in the United States of America.
  37.  
  38.  1 2 3 4 5 6 7 8 9    FGFG    3 2 1 0 9 8
  39.  
  40.  Distributed to the book trade in the United States by Harper & Row.
  41.  
  42.  Distributed to the book trade in Canada by General Publishing Company, Ltd.
  43.  
  44.  Distributed to the book trade outside the United States and Canada by
  45.  Penguin Books Ltd.
  46.  
  47.  Penguin Books Ltd., Harmondworth, Middlesex, England
  48.  Penguin Books Australia Ltd., Ringwood, Victoria, Australia
  49.  Penguin Books N.Z. Ltd., 182-190 Wairu Road, Auckland 10, New Zealand
  50.  
  51.  British Cataloging in Publication Data available
  52.  
  53.  IBM(R), PC/AT(R), and PS/2(R) are registered trademarks of International
  54.  Business Machines Corporation.
  55.  
  56.  CodeView(R), Microsoft(R), MS-DOS(R), and XENIX(R) are registered trademarks
  57.  of Microsoft Corporation.
  58.  
  59.  UNIX TM is a trademark of AT&T Bell Laboratories.
  60.  
  61.  ────────────────────────────────────────────────────────────────────────────
  62.  Acquisitions Editor: Claudette Moore  Project Editor: Eric Stroo
  63.  Technical Editor: Mike Halvorson
  64.  ────────────────────────────────────────────────────────────────────────────
  65.  
  66.  
  67.  
  68.                                    Dedication
  69.  
  70.                                   For Kathleen
  71.  
  72.  
  73.  
  74.  ────────────────────────────────────────────────────────────────────────────
  75.  Contents
  76.  
  77.    Road Map to Important Figures and Tables
  78.  
  79.    Acknowledgments
  80.  
  81.    Introduction
  82.  
  83.    SECTION 1  THE OS/2 OPERATING SYSTEM
  84.  
  85.    Chapter 1  Introduction to OS/2
  86.               System positioning; Key features of OS/2
  87.  
  88.    Chapter 2  OS/2 Structure and Initialization
  89.               System components; How OS/2 is loaded; Memory maps
  90.  
  91.    Chapter 3  OS/2 Application Programs
  92.               Program source structure; Module definition files; Executable
  93.               file structure; Program loading, entry conditions, and
  94.               termination; Sample program: HELLO.ASM
  95.  
  96.    Chapter 4  OS/2 Programming Tools
  97.               File types and extensions; Macro Assembler; C Compiler; Linker;
  98.               CREF utility; Librarian; BIND and API.LIB; MAKE utility
  99.  
  100.    SECTION 2  PROGRAMMING THE USER INTERFACE
  101.  
  102.    Chapter 5  Keyboard and Mouse Input
  103.               Keyboard modes; Keyboard input methods; Use of logical
  104.               keyboards; Keyboard subsystems and drivers; Keyboard monitors;
  105.               Mouse input methods; Mouse subsystems and drivers; Sample
  106.               program: MOUDEMO.C
  107.  
  108.    Chapter 6  The Video Display
  109.               Video display modes; Using DosWrite for output; Using Vio
  110.               functions for output; Pop-up support; Video subsystems and
  111.               drivers
  112.  
  113.    Chapter 7  Printer and Serial Ports
  114.               Printer output methods; The print spooler; Serial port input
  115.               and output; Configuring the serial port; The serial port and
  116.               real mode applications
  117.  
  118.    SECTION 3  PROGRAMMING MASS STORAGE
  119.  
  120.    Chapter 8  File Management
  121.               Filenames and handles; Opening, creating, and closing files;
  122.               Reading and writing files; Forcing disk updates; File and
  123.               record locking; Sample programs: ARGC.ASM, ARGV.ASM, DUMP.C,
  124.               DUMP.ASM
  125.  
  126.    Chapter 9  Volumes and Directories
  127.               Hierarchical directory structures; Searching disk directories;
  128.               Directory and disk control; Volume labels; Sample programs:
  129.               WHEREIS.C and WHEREIS.ASM
  130.  
  131.    Chapter 10 Disk Internals
  132.               Boot sector and BIOS parameter block; File allocation table;
  133.               Root directory; Files area and subdirectories; Fixed disk
  134.               partitions; Direct disk access
  135.  
  136.    SECTION 4  ADVANCED OS/2 TECHNIQUES
  137.  
  138.    Chapter 11 Memory Management
  139.               Memory protection; Virtual memory; Allocating global memory;
  140.               Huge memory blocks; Discardable segments; Writable
  141.               code segments; Local storage
  142.  
  143.    Chapter 12 Multitasking
  144.               The OS/2 scheduler; Managing processes; Managing threads; Managi
  145.               sessions; Configuring the OS/2 multitasker; Sample programs:
  146.               TINYCMD.C, TINYCMD.ASM, DUMBTERM.C, DUMBTERM.ASM
  147.  
  148.    Chapter 13 Interprocess Communication
  149.               Semaphores; Pipes; Shared memory; Queues; Signals
  150.  
  151.    Chapter 14 IOPL Segments
  152.               Execution with I/O privilege; IOPL-related API functions; Using
  153.               IOPL in applications; Sample programs: PORTS.C, PORTS.ASM,
  154.               PORTIO.ASM
  155.  
  156.    Chapter 15 Time, Date, and Timers
  157.               Reading and setting date and time; Programmed delays; Using
  158.               timers
  159.  
  160.    SECTION 5  CUSTOMIZING OS/2
  161.  
  162.    Chapter 16 Filters
  163.               System support for filters; Building simple filters; Using a
  164.               filter as a child process; Sample programs: PROTO.C, PROTO.ASM,
  165.               FIND.C, EXECSORT.ASM
  166.  
  167.    Chapter 17 Device Drivers
  168.               Unique aspects of OS/2 drivers; Device driver types and modes;
  169.               Device driver structure; Command code functions; Device Driver
  170.               Helper functions (DevHlps); Building a device driver; Sample
  171.               programs: TEMPLATE.ASM, TINYDISK.ASM
  172.  
  173.    Chapter 18 Device Monitors
  174.               Monitor organization; Monitor data packets; Device driver
  175.               support for monitors; Sample programs: SNAP.C, SNAP.ASM
  176.  
  177.    Chapter 19 Dynamic Link Libraries
  178.               Dynamic linking at load time; Dynamic linking at run time;
  179.               Writing DLLs in assembly language; Writing DLLs in C; Sample
  180.               programs: ASMHELP.ASM, SHOWARGS.ASM, CDLL.C, CINIT.ASM
  181.  
  182.    SECTION 6  OS/2 REFERENCE
  183.  
  184.    Part 1  Kernel API
  185.  
  186.    Part 2  DosDevIOCtl Functions
  187.  
  188.    Part 3  DevHlp Functions
  189.  
  190.    SECTION 7  APPENDIXES
  191.  
  192.    Appendix A  OS/2 Error Codes
  193.  
  194.    Appendix B  ASCII and IBM Extended Character Sets
  195.  
  196.    Appendix C  Resources
  197.  
  198.    Appendix D  OS/2 Load Module Format
  199.  
  200.    Appendix E  Module Definition File Syntax
  201.  
  202.    Glossary
  203.  
  204.    Index
  205.  
  206.  
  207.  
  208.  ────────────────────────────────────────────────────────────────────────────
  209.  Road Map to Important Figures and Tables
  210.  
  211.    OS/2 kernel device drivers
  212.  
  213.    OS/2 kernel dynlink libraries
  214.  
  215.    OS/2 memory maps
  216.  
  217.    Segments, groups, and classes
  218.  
  219.    Module definition file syntax for applications
  220.  
  221.    Application program entry conditions
  222.  
  223.    Microsoft MASM switches and options
  224.  
  225.    Microsoft C switches and options
  226.  
  227.    Microsoft LINK switches and options
  228.  
  229.    Video attributes and colors
  230.  
  231.    DosOpen modes, flags, and attributes
  232.  
  233.    DosFindFirst/DosFindNext search buffer format
  234.  
  235.    Boot sector layout
  236.  
  237.    Disk directory layout
  238.  
  239.    Partition table layout
  240.  
  241.    Conforming API functions
  242.  
  243.    Time and date formats
  244.  
  245.    Device driver attribute word
  246.  
  247.    Device driver command codes
  248.  
  249.    BIOS parameter block
  250.  
  251.    Medium ID bytes
  252.  
  253.    Device Driver Helpers (DevHlps) by category
  254.  
  255.    Keyboard monitor data packet
  256.  
  257.    Mouse and printer monitor data packets
  258.  
  259.    Module definition file syntax for dynlink libraries
  260.  
  261.    Dynlink library initialization entry conditions
  262.  
  263.  
  264.  
  265.  ────────────────────────────────────────────────────────────────────────────
  266.  Acknowledgments
  267.  
  268.    In a book project of this size and duration, the list of people involved
  269.    at every level──technical review, editing, design, word processing,
  270.    typography, proofreading, and marketing──grows extremely long. Although I
  271.    cannot thank each of these people individually, I hope they know how
  272.    much I appreciate their fine and patient work. Special thanks to Pat
  273.    Combs, Mike Halvorson, Claudette Moore, and Eric Stroo for their editorial
  274.    contributions, and to David Maritz, Anthony Short, Ben Slivka, and Mark
  275.    Zbikowski for technical assistance beyond the call of duty.
  276.  
  277.  
  278.  
  279.  ────────────────────────────────────────────────────────────────────────────
  280.  Introduction
  281.  
  282.    Operating System/2, Microsoft's protected mode operating system for
  283.    80286-based and 80386-based microcomputers, provides programmers with a
  284.    powerful new platform for application design. It also challenges them to
  285.    assimilate a body of technical information whose size is unprecedented in
  286.    the microcomputer world. The reference manuals for OS/2 and its extensions
  287.    (such as the Presentation Manager and LAN Manager) already fill several
  288.    shelves──only a year after the system was first released──and the
  289.    Microsoft or IBM Programmer's Toolkit, along with the necessary
  290.    development tools and libraries, can devour a sizable fixed disk.
  291.  
  292.    No single book can cover everything you need to know about OS/2
  293.    programming; there are no shortcuts to mastery of such a complex system,
  294.    and there are few simple answers. This particular book, Advanced OS/2
  295.    Programming, focuses solely on the OS/2 kernel and its facilities for
  296.    character-oriented input and output, file access, virtual memory
  297.    management, multitasking, interprocess communication, dynamic linking, and
  298.    the like. Graphically oriented Presentation Manager applications and the
  299.    Presentation Manager itself are mentioned only in passing; for further
  300.    information on these topics, see Charles Petzold's excellent book,
  301.    Programming the OS/2 Presentation Manager (also from Microsoft Press).
  302.  
  303.    Advanced OS/2 Programming is essentially divided into six sections, which
  304.    reflects my prejudices about how the operating system should be studied.
  305.  
  306.    Section One is descriptive: It begins with an introduction to the
  307.    important features of OS/2, followed by an explanation of OS/2's
  308.    components and initialization, a discussion of the structure of OS/2
  309.    application programs, and an operational summary of the Microsoft
  310.    programming tools.
  311.  
  312.    Sections Two and Three address the fundamental concerns of every
  313.    application programmer: management of the keyboard, display, pointing
  314.    device, and mass storage. A chapter on the internal structure of FAT
  315.    (MS-DOS─compatible) file systems is also included, although this material
  316.    will become less important in subsequent versions of OS/2, for which
  317.    support of installable file systems is planned.
  318.  
  319.    Section Four is devoted to virtual memory management, multitasking,
  320.    interprocess communication, direct hardware control, and programmable
  321.    timers. For most programmers with an MS-DOS background, this section will
  322.    be both the most interesting and the most relevant to exploiting fully the
  323.    capabilities of OS/2.
  324.  
  325.    Finally, Section Five contains information on four special classes of
  326.    OS/2 programs: filters, device monitors, device drivers, and dynlink
  327.    libraries. Although you will probably never need to write one of these
  328.    types of programs, you might still find it useful to browse these chapters
  329.    for a general sense of how such programs work and are put together.
  330.  
  331.    The remainder of the book──Section Six and the Appendixes──is intended
  332.    to fill all the reference needs of the typical kernel application
  333.    programmer. All the OS/2 kernel services made available to application
  334.    programs, the I/O control subfunctions, and the Device Driver Helper
  335.    (DevHlp) functions are described with their arguments and results and with
  336.    cross-references to related functions. The OS/2 error codes, executable
  337.    file format, and module definition (DEF) file syntax are also covered in
  338.    detail.
  339.  
  340.    Each major OS/2 programming issue and concept is illustrated with complete
  341.    sample programs in both C and assembly language. For linking the sample
  342.    programs, OS2.LIB and DOSCALLS.LIB are interchangeable. The programs were
  343.    written with Solution System's BRIEF editor and assembled or compiled with
  344.    Microsoft MASM version 5.1 and Microsoft C version 5.1. They have been
  345.    tested under OS/2 versions 1.0 and 1.1 on a Compaq Portable 286, a Compaq
  346.    DeskPro 386, and an IBM PS/2 Model 80. As far as I know, they contain no
  347.    software or hardware dependencies that prevent their running properly on
  348.    any IBM PC/AT─compatible or PS/2─compatible machine running OS/2 version
  349.    1.0 or 1.1.
  350.  
  351.    To make the sample programs useful to programmers using other languages or
  352.    non-Microsoft programming tools, I have endeavored to make them as
  353.    self-contained and "generic" as possible. The source listings do not rely
  354.    on any header files found in the Microsoft or IBM Programmer's Toolkit,
  355.    the special reentrant runtime libraries supplied with Microsoft C version
  356.    5.1, or the new constructs and directives that were added to Microsoft
  357.    MASM in version 5.0. For the same reasons, the reference sections shun the
  358.    macros, arbitrary namings, manifest constants, and customized data types
  359.    (typedefs) which pervade the Microsoft OS/2 Programmer's Reference.
  360.  
  361.    Please feel free to contact me with your questions, comments, and
  362.    suggestions for future editions via MCI Mail (user name LMI), CompuServe
  363.    (user ID 72406,1577), or BIX (user name rduncan).
  364.  
  365.                                                                    Ray Duncan
  366.                                                       Los Angeles, California
  367.                                                                 December 1988
  368.  
  369.  
  370.  
  371.  ────────────────────────────────────────────────────────────────────────────
  372.  SECTION 1  THE OS/2 OPERATING SYSTEM
  373.  ────────────────────────────────────────────────────────────────────────────
  374.  
  375.  
  376.  
  377.  ────────────────────────────────────────────────────────────────────────────
  378.  Chapter 1  Introduction to OS/2
  379.  
  380.    OS/2 is the Microsoft multitasking, virtual memory, single-user operating
  381.    system for personal computers based on the Intel 80286 and 80386
  382.    microprocessors. It is the first software product to be brought to market
  383.    as a result of the Joint Development Agreement signed by IBM and Microsoft
  384.    in August 1985.
  385.  
  386.    OS/2 is positioned between the MS-DOS single-tasking operating system and
  387.    the XENIX/UNIX multi-user multitasking operating system (Figure 1-1 on
  388.    the following page). Although it is compatible with MS-DOS file systems
  389.    and can run many existing MS-DOS applications, and although it has a
  390.    hierarchical directory structure, I/O redirection, and some interprocess
  391.    communication mechanisms similar to XENIX/UNIX, OS/2 is neither an
  392.    overblown MS-DOS nor a stripped-down XENIX/UNIX. It is a completely new
  393.    operating system, designed to support high performance, intensely
  394.    interactive applications in a business environment.
  395.  
  396.    During the next few years, the increasing dominance of 80286- and
  397.    80386-based machines, and the accompanying penetration of OS/2, will have
  398.    a vast influence on the work habits and productivity of computer users. It
  399.    will also force many changes on programmers and software publishers.
  400.    Although porting existing MS-DOS products to OS/2 is a straightforward
  401.    process, taking full advantage of OS/2's capabilities will involve a
  402.    wholesale rethinking and redesign of most applications. In return, OS/2
  403.    offers programmers a "clean slate" and the opportunity to set new
  404.    standards.
  405.  
  406.    ╔══════════════════════╗
  407.    ║                      ║ 8086/88/286/386-based personal computers
  408.    ║      MS-DOS 4.0      ║ Single-tasking
  409.    ║                      ║ Single-user, character interface
  410.    ╚══════════════════════╝
  411.    ╔══════════════════════╗
  412.    ║      MS-DOS 4.0      ║ 8086/88/286/386-based personal computers
  413.    ║         and          ║ Nonpreemptive multitasking
  414.    ║     Windows 2.1      ║ Single-user, graphical interface
  415.    ╚══════════════════════╝
  416.    ┌──────────────────────┐
  417.    │                      │ 80286/386-based personal computers
  418.    │     MS OS/2 1.0      │ Preemptive multitasking
  419.    │                      │ Single-user, character interface
  420.    └──────────────────────┘
  421.    ┌──────────────────────┐
  422.    │     MS OS/2 1.1      │ 80286/386-based personal computers
  423.    │         and          │ Preemptive multitasking
  424.    │ Presentation Manager │ Single-user, graphical interface
  425.    └──────────────────────┘
  426.    ╔══════════════════════╗
  427.    ║                      ║ 80286/386-based personal computers
  428.    ║        XENIX         ║ Preemptive multitasking
  429.    ║                      ║ Multi-user, character interface
  430.    ╚══════════════════════╝
  431.  
  432.    Figure 1-1.  The Microsoft family of operating systems for personal
  433.    computers based on the Intel 80x86 line of microprocessors.
  434.  
  435.  
  436.  Key Features of OS/2
  437.  
  438.    From the programmer's point of view, the key features of software
  439.    development under OS/2 are the following:
  440.  
  441.    ■  A graphical user interface
  442.  
  443.    ■  A new Application Program Interface (API)
  444.  
  445.    ■  Memory protection and virtual memory
  446.  
  447.    ■  Preemptive multitasking
  448.  
  449.    ■  Interprocess communication facilities
  450.  
  451.    ■  Dynamic linking
  452.  
  453.    ■  MS-DOS compatibility
  454.  
  455.    These features have many important implications for the structure of the
  456.    operating system itself, and for the design and operation of application
  457.    programs.
  458.  
  459.  
  460.  Graphical User Interface
  461.  
  462.    The OS/2 graphical user interface is called the Presentation Manager. With
  463.    Presentation Manager facilities, the user can view multiple applications
  464.    at the same time in windows (subdivisions of the screen), whose size,
  465.    shape, and position the user can control. The Presentation Manager offers
  466.    the programmer a wide variety of vector and raster graphics operations and
  467.    extensive font support, including multiple font styles and sizes; it
  468.    allows the development of hardware-independent applications that can run
  469.    on a broad range of all-points-addressable (APA) output devices.
  470.  
  471.    Application programs written to run under the Presentation Manager have a
  472.    common appearance and style of interaction, using pull-down menus, scroll
  473.    bars, and dialog boxes. The user can select from menus with either a mouse
  474.    or the keyboard, and can cut and paste data and graphic images from the
  475.    domain of one program into another in a "natural" manner. These features
  476.    help users work more efficiently, and they shorten the learning curve for
  477.    new application programs.
  478.  
  479.  
  480.  The Application Program Interface
  481.  
  482.    The OS/2 kernel──the part of the operating system that manages files,
  483.    memory, multitasking, timers, and interprocess communications──provides
  484.    about 250 services to application programs. The Presentation Manager
  485.    supports an additional 500-odd functions related to window management and
  486.    the graphical user interface. These services, known collectively as the
  487.    Application Program Interface (API), are invoked by far calls to
  488.    individual named entry points. Parameters for API functions are passed on
  489.    the stack, and values are returned in variables (commonly structures) in
  490.    the caller's memory space. Addresses are always passed or returned in the
  491.    form of far pointers (selector and offset).
  492.  
  493.    Although the OS/2 API is a considerable architectural change from the
  494.    familiar INT 21H of MS-DOS, its advantages are significant. The API allows
  495.    OS/2 to take full advantage of the 80286's hardware mechanisms for
  496.    parameter passing and to enforce the separation between kernel and user
  497.    privilege levels. It is compatible with the procedure-calling conventions
  498.    of most high level languages, allowing programs written in high level
  499.    languages to access operating system services directly without the need
  500.    for intermediary library functions. And the API can easily be extended for
  501.    future 32-bit versions of the operating system that will take greater
  502.    advantage of the 80386 and its successors.
  503.  
  504.  
  505.  Memory Protection and Virtual Memory
  506.  
  507.    The CPUs in the Intel 80x86 family generate memory addresses by combining
  508.    the contents of segment registers (which can be thought of as base
  509.    pointers) with an offset, or displacement. The offset can be specified in
  510.    the machine instruction or in one or more index registers (or in both
  511.    places). The way that the hardware interprets a value in a segment
  512.    register depends on whether the processor is running in real mode or
  513.    protected mode.
  514.  
  515.    In real mode, the value in a segment register is a paragraph address: a
  516.    20-bit physical address divided by 16. To form a complete memory address,
  517.    the processor shifts the contents of a segment register to the left four
  518.    bits and adds a 16-bit offset. In real mode, the hardware provides no
  519.    memory protection mechanisms; any program can read or write any memory
  520.    address or access any I/O port. MS-DOS and its applications execute in
  521.    real mode, regardless of the type of processor.
  522.  
  523.    In protected mode, a level of addressing indirection is added. Segment
  524.    registers do not point directly to the base of an area of memory; instead,
  525.    they contain values called selectors. A selector is an index to an entry
  526.    in a descriptor table; the entry contains the base address and length of a
  527.    memory segment, its attributes (executable, read-only, or read-write), and
  528.    privilege information. Each time a program references memory, the hardware
  529.    uses information from the descriptor table to generate the physical
  530.    address and simultaneously validates the memory access. OS/2 executes on
  531.    the 80286 or 80386 in protected mode; the 8086 and 8088 processors do not
  532.    support protected mode, which is the reason they cannot run OS/2.
  533.  
  534.    Because only the operating system can manipulate descriptor tables, which
  535.    govern the addressable memory space of all programs, a protected mode
  536.    operating system can completely isolate programs from one another. If a
  537.    program attempts to read or write memory that does not belong to it, or if
  538.    it calls a routine to which it has not been given access, a CPU fault
  539.    occurs and the operating system regains control. The presence of this
  540.    memory protection makes for a very robust programming and debugging
  541.    environment: An aberrant program rarely brings down the entire system.
  542.  
  543.    Protected mode also provides privilege levels that can be used to
  544.    constrain application programs. The four hardware-enforced privilege
  545.    levels are referred to as ring 0, ring 1, ring 2, and ring 3. Applications
  546.    ordinarily execute at ring 3 (the lowest privilege level); if a ring 3
  547.    program attempts to clear interrupts, read or write I/O ports, or modify
  548.    CPU registers that would compromise memory protection, a CPU fault is
  549.    generated and the program is terminated. The operating system kernel
  550.    executes at ring 0, which provides unrestricted access to all
  551.    instructions, memory addresses, and I/O ports. OS/2 does not use ring 1 at
  552.    all. A limited class of applications that require direct access to
  553.    adapters (such as non─Presentation Manager graphics applications) are
  554.    allowed to run at ring 2 and perform I/O operations.
  555.  
  556.    The flip side of the memory protection coin is virtual memory. OS/2 can
  557.    manage as much as 16 MB of physical memory, but the amount of installed
  558.    RAM is nearly irrelevant to the average application program running in
  559.    protected mode. When the sum of the memory owned by active programs in the
  560.    system exceeds the actual amount of physical memory, memory segments are
  561.    rolled in and out as needed from a swap file (or simply discarded and
  562.    reloaded in the case of code segments or read-only data segments). This
  563.    segment swapping is accomplished by an OS/2 module known as the Memory
  564.    Manager, with the aid of the processor's hardware memory protection
  565.    mechanisms, and is completely invisible to application programs. The
  566.    theoretical limit on the amount of memory a program can own or share is
  567.    about 128 MB (in OS/2 version 1.1), but the practical limit is the amount
  568.    of physical RAM plus the free space on the logical drive that contains the
  569.    swap file.
  570.  
  571.  
  572.  Preemptive Multitasking
  573.  
  574.    Preemptive multitasking (sometimes called time-slicing) refers to OS/2's
  575.    ability to allocate processor time among multiple programs in a manner
  576.    that is invisible to those programs. A hardware interrupt called the timer
  577.    tick, generated by a programmable timer chip, allows the operating system
  578.    to regain control at predetermined intervals. After updating the current
  579.    date and time, the timer tick interrupt handler transfers control to the
  580.    scheduler, which maintains a list of the active tasks and their states and
  581.    decides which piece of code will run next.
  582.  
  583.    For the user, the interface to OS/2's multitasking capabilities is simple
  584.    and easy to understand. A user starts a program by picking its name from a
  585.    menu or by entering the name at a command prompt. OS/2 programs that are
  586.    compatible with the Presentation Manager can run within windows, whereas
  587.    those that are not compatible occupy the full screen. In either case, when
  588.    more than one program is running, the user can press hot keys or choose
  589.    from a menu to select a program with which to interact.
  590.  
  591.    The programmer can control OS/2 multitasking at three levels of system
  592.    activity: processes, threads, and sessions. The simplest case of a process
  593.    is similar to a program loaded under MS-DOS: A process is initiated when
  594.    the operating system allocates some memory, loads the necessary code and
  595.    data from a disk file, and gives it control at an entry point that is
  596.    specified in the file. Subsequently, the process can obtain and release
  597.    additional resources, such as memory segments, disk files, interprocess
  598.    communication facilities, and timers.
  599.  
  600.    When a process starts, it contains a single point of execution: its
  601.    primary thread. That thread can in turn create other threads, all of which
  602.    share equal access to the memory, files, and other resources owned by the
  603.    process. The OS/2 scheduler deals with threads rather than with processes;
  604.    for each active thread, it keeps track of the state (executing, ready to
  605.    execute, or waiting for some event), priority, register contents and
  606.    flags, and instruction pointer. The ability to decompose an application
  607.    into multiple asynchronous threads, which can communicate rapidly through
  608.    a common data space, is an extremely powerful feature of OS/2.
  609.  
  610.    Each process is a member of a session (sometimes called a screen group),
  611.    which is associated with a virtual console: a virtual screen, keyboard,
  612.    and mouse. When the user chooses a session, the virtual devices are bound
  613.    to the physical devices, and the user can interact with the programs in
  614.    that session. OS/2 can support as many as 12 protected mode sessions and 1
  615.    real mode user session, and each session can contain one or more
  616.    processes. (The Presentation Manager and all its applications, for
  617.    example, run in a single session.)
  618.  
  619.  
  620.  Interprocess Communication
  621.  
  622.    OS/2 supports all the methods of interprocess communication (IPC) that are
  623.    found in other multitasking operating systems:
  624.  
  625.    Semaphores are simple IPC objects that have only two states: set (or
  626.    owned) and cleared (or not owned). They are used between threads or
  627.    processes for signaling and for synchronization of access to any type of
  628.    resource. OS/2 supports several different types of semaphores to meet a
  629.    variety of speed and security needs.
  630.  
  631.    Pipes, as in XENIX/UNIX, allow high performance transfer of variable
  632.    length messages. A process can read and write them much as it does files,
  633.    except that the data is always kept resident in memory. Unnamed
  634.    (anonymous) pipes are used by closely related processes running on the
  635.    same machine, whereas named pipes allow communication between unrelated
  636.    processes running on the same or different machines.
  637.  
  638.    Shared memory segments can be accessed by any process that can reference
  639.    the segment by name. IPC by shared memory is potentially the fastest
  640.    available mechanism, because its speed is limited only by the rate at
  641.    which bytes can be copied from one place to another.
  642.  
  643.    Queues are named, global objects and can be thought of as structured lists
  644.    of shared memory segments. Queue records can be ordered by
  645.    first-in-first-out (FIFO), last-in-first-out (LIFO), or by an assigned
  646.    priority, and a queue's size is limited only by the number of available
  647.    selectors and the disk swap space.
  648.  
  649.    Signals are similar to their implementation in XENIX/UNIX; they simulate
  650.    an interrupt for the receiving process in that they divert its execution
  651.    immediately to a signal handler. The system provides a default handler for
  652.    each signal type; the process can register its own handler for a
  653.    particular type, or it can notify the system to ignore the signal.
  654.  
  655.  
  656.  Dynamic Linking
  657.  
  658.    The 80286 and 80386 support for protected, virtual memory makes it
  659.    possible to place frequently used procedures into special files known as
  660.    dynamic link (dynlink) libraries. Dynlink libraries are, in essence,
  661.    shareable subroutine libraries which are loaded on demand. Placing common
  662.    procedures in dynlink libraries allows those routines to be altered,
  663.    improved, or replaced without changing the application programs which
  664.    invoke them. Their use also conserves both RAM and disk storage, because
  665.    the size of each individual EXE file is reduced, and because the same copy
  666.    of a dynlink library procedure in memory is shared by all the processes
  667.    which use it. The dynlink library concept is so powerful and so widely
  668.    useful that most OS/2 kernel and Presentation Manager functions available
  669.    to application programs are distributed in this form.
  670.  
  671.    Calls from a program to routines in a dynamic link library are resolved in
  672.    two stages. The Linker is informed that a particular external name is a
  673.    dynlink routine either by an IMPORTS statement in the program's module
  674.    definition file or by the occurrence of a special dynlink reference record
  675.    in an object module library. It then builds the information necessary for
  676.    dynamic linking into the program's EXE file header: the names of the
  677.    dynlink routines that are needed, the names of the dynlink library files
  678.    in which those routines are found, and a list for each routine of all the
  679.    addresses within the program at which it is called. When the application
  680.    is loaded for execution, OS/2 examines the list of imported routines,
  681.    fetches from disk any external routines that are not already resident in
  682.    memory, and fixes up the addresses within the calling program. This
  683.    technique is sometimes called late binding.
  684.  
  685.  
  686.  MS-DOS Compatibility
  687.  
  688.    OS/2 provides upward compatibility and a smooth transition from MS-DOS at
  689.    three levels: the user interface, the file system, and the DOS
  690.    compatibility mode (sometimes called the 3.x Box).
  691.  
  692.    The command line interface of OS/2 is virtually identical to that of
  693.    MS-DOS versions 2.x and 3.x, with the exception of a few new or enhanced
  694.    commands, batch file directives, and CONFIG.SYS file options. Similarly,
  695.    the "desktop" of the OS/2 Presentation Manager, with its overlapping
  696.    windows, pull-down menus, and dialog boxes, closely resembles the visual
  697.    shell of MS-DOS version 4.
  698.  
  699.    The file structure for both flexible and fixed disks──that is, the layout
  700.    of the partition table, directories, file allocation tables, and the files
  701.    area──is exactly the same for the initial release of OS/2 as it is for
  702.    MS-DOS. Developers can therefore exchange files and move back and forth
  703.    between the two environments with a minimum of difficulty. A future
  704.    release of OS/2 will support installable file systems, which will relieve
  705.    users of the historical MS-DOS limitations on volume sizes and filename
  706.    lengths.
  707.  
  708.    DOS compatibility mode is a component of the OS/2 operating system that
  709.    allows a single "old," real mode (MS-DOS) application to run alongside
  710.    "new," protected mode applications. OS/2 traps MS-DOS function calls by
  711.    the real mode application and translates them into API calls, switching
  712.    back and forth between real mode and protected mode as necessary to
  713.    perform I/O and other services. DOS compatibility mode also provides a
  714.    realistic-looking milieu for more hardware-dependent MS-DOS programs by
  715.    supporting certain "undocumented" MS-DOS services and internal flags, by
  716.    supplying a "clock tick" interrupt at the appropriate frequency, by
  717.    maintaining a ROM BIOS data area at segment 40H, and so forth.
  718.  
  719.    The user can determine how much memory the system allocates to DOS
  720.    compatibility mode by adding a directive in the CONFIG.SYS file, or the
  721.    user can disable the mode completely. One disadvantage of supporting
  722.    compatibility mode is that the entire system is left vulnerable.
  723.    "Ill-behaved" MS-DOS programs can crash the system, an unfortunate
  724.    trade-off for allowing the real mode programs to be used at all.
  725.  
  726.  
  727.  
  728.  ────────────────────────────────────────────────────────────────────────────
  729.  Chapter 2  OS/2 Structure and Initialization
  730.  
  731.    The software that runs on present-day computer systems is, by convention,
  732.    organized into layers with varying degrees of independence from the
  733.    characteristics of the underlying hardware. The objectives of this
  734.    layering are to minimize the impact on programs of differences among
  735.    hardware devices or changes in the hardware, to centralize and optimize
  736.    the code for common operations, and to simplify the task of moving
  737.    programs and their data from one machine to another.
  738.  
  739.    To give you a feel for the territory, let's divide the software in a
  740.    system into three layers, as shown in Figure 2-1 on the following page.
  741.    In this simplified view of a system, the top layer is the least
  742.    hardware-dependent. It is composed of application programs, which perform
  743.    a specific job, interact with the user, and manipulate data in units of
  744.    files and records. Such programs are sometimes called transient because
  745.    they are brought into RAM (random access memory) for execution when they
  746.    are needed and then discarded from memory when their job is finished.
  747.  
  748.    The middle layer is the operating system kernel, which allocates system
  749.    resources such as memory and peripheral devices, implements the file
  750.    system, and provides a host of hardware-independent services to
  751.    application programs. The kernel is usually brought into memory from disk
  752.    when you turn on or restart the system, and it remains fixed in memory
  753.    until the system is turned off.
  754.  
  755.    The lowest software layer is made up of specialized programs called device
  756.    drivers. Device drivers are responsible for transferring data between a
  757.    peripheral device (such as a disk or terminal) and RAM, where it can be
  758.    used by other programs. Drivers shield the operating system kernel from
  759.    the need to deal with the I/O port addresses of the hardware, its
  760.    operating characteristics, and the peculiarities of a particular
  761.    peripheral device, just as the kernel in turn shields application programs
  762.    from the details of memory and file management.
  763.  
  764.    With these basic concepts in hand, let's take a look at the actual
  765.    organization of OS/2 (still recognizing, however, that the real operating
  766.    system is more complicated than either model).
  767.  
  768.    ┌─────────────────────────┐──┐
  769.    │      Applications       │  │
  770.    └─────┬──────────────────┘  │
  771.          │             │        │
  772.          │             │        │
  773.    ┌──────────────────┴─────┐  │
  774.    │ Operating system kernel │  ├── Software
  775.    └─────┬──────────────────┘  │
  776.          │             │        │
  777.          │             │        │
  778.    ┌──────────────────┴─────┐  │
  779.    │     Device drivers      │  │
  780.    └─────┬──────────────────┘──┘
  781.          │             │
  782.          │             │
  783.    ╔══════════════════╧═════╗
  784.    ║        Hardware         ║
  785.    ╚═════════════════════════╝
  786.  
  787.    Figure 2-1.  A simplistic view of OS/2 system layers.
  788.  
  789.  
  790.  The Structure of OS/2
  791.  
  792.    The major elements of the OS/2 operating system are the following:
  793.  
  794.    ■  Device drivers
  795.  
  796.    ■  Kernel
  797.  
  798.    ■  Dynlink libraries
  799.  
  800.    ■  Command processor
  801.  
  802.    ■  Presentation Manager
  803.  
  804.    The operating system components interact in complicated ways, and a
  805.    particular system capability is not always located where you might expect.
  806.    Some OS/2 components execute at the kernel level (ring 0), some in user
  807.    mode with I/O privilege (ring 2), and some at the lowest privilege level
  808.    (ring 3), yet this distinction is generally invisible to application
  809.    programs (Figure 2-2).
  810.  
  811.    ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
  812.    │    Kernel    │ │    Kernel    │ │   CMD.EXE    │ │ Presentation │ ...etc.
  813.    │    App #1    │ │    App #2    │ │              │ │   Manager    │
  814.    └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
  815.    ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
  816.    │ VIOCALLS │ │ KDBCALLS │ │ MOUCALLS │ │ DOSCALL1 │ │  SESMGR  │ ...etc.
  817.    │   .DLL   │ │   .DLL   │ │   .DLL   │ │   .DLL   │ │   .DLL   │
  818.    └──────────┘ └──────────┘ └──────────┘ │          │ └──────────┘
  819.                                           │          │
  820.    ┌──────────┐ ┌──────────┐ ┌──────────┐ │          │
  821.    │ BVSCALLS │ │ BKSCALLS │ │ BMSCALLS │ │          │ ...etc.
  822.    │   .DLL   │ │   .DLL   │ │   .DLL   │ │          │          Ring 3
  823.    │----------│-│----------│-│----------│-│----------│----------------
  824.    └──────────┘ └──────────┘ └──────────┘ └──────────┘          Ring 2
  825.     ------------------------------------------------------------------
  826.                                                                 Ring 0
  827.             ┌──────────────────┐
  828.             │   OS/2 kernel    │
  829.             └──────────────────┘
  830.             ┌──────────────────┐
  831.             │  Device drivers  │
  832.             └──────────────────┘
  833.             ╔══════════════════╗
  834.             ║     Hardware     ║
  835.             ╚══════════════════╝
  836.  
  837.    Figure 2-2.  A less simplistic view of OS/2 system layers. The OS/2 kernel
  838.    and device drivers run at ring 0 and have unrestricted access to all I/O
  839.    ports and memory addresses. Application programs and dynlink libraries
  840.    normally run in ring 3 and cannot access the hardware; ring 3 programs are
  841.    fully protected from each other. Some application programs and dynlink
  842.    libraries contain I/O privilege segments and are allowed to perform I/O
  843.    operations in ring 2; however, servicing of interrupts is reserved for
  844.    ring 0 routines. Presentation Manager applications and dynlink libraries
  845.    are not shown here.
  846.  
  847.  Device Drivers
  848.  
  849.    The basic OS/2 system includes a large number of device drivers, which are
  850.    supplied in individual files with the SYS extension (Figure 2-3). A "base
  851.    set" of device drivers for the keyboard, display, floppy disk, fixed disk,
  852.    printer, and real time clock is always loaded during system
  853.    initialization. These drivers are selected automatically from the drivers
  854.    on the boot disk based on the system type (PC/AT or PS/2). The other
  855.    standard OS/2 drivers, such as ANSI.SYS, MOUSEA01.SYS, POINTDD.SYS,
  856.    COM01.SYS, and EGA.SYS, are loaded only if the appropriate DEVICE
  857.    directive is present in the CONFIG.SYS file. Drivers supplied by third
  858.    party manufacturers can also be loaded during initialization with DEVICE
  859.    directives.
  860.  
  861. ╓┌─┌────────────────┌────────────────────────────────────────────────────────╖
  862.    Driver           Description
  863.    Driver           Description
  864.    ──────────────────────────────────────────────────────────────────────────
  865.    ANSI.SYS         ANSI console driver for real mode (logical device CON)
  866.  
  867.    CLOCK01.SYS      Real time clock driver for PC/AT (logical device CLOCK$)
  868.  
  869.    CLOCK02.SYS      Real time clock driver for PS/2 (logical device CLOCK$)
  870.  
  871.    COM01.SYS        Serial communications port driver for PC/AT (logical
  872.                     devices COM1 and COM2)
  873.  
  874.    COM02.SYS        Serial communications port driver for PS/2 (logical
  875.                     devices COM1, COM2, and COM3)
  876.  
  877.    COUNTRY.SYS      Country-dependent information, such as collating tables
  878.                     and time, date, and currency formats
  879.  
  880.    DISK01.SYS       Floppy disk and fixed disk driver for PC/AT
  881.  
  882.    DISK02.SYS       Floppy disk and fixed disk driver for PS/2
  883.  
  884.    Driver           Description
  885.    ──────────────────────────────────────────────────────────────────────────
  886. 
  887.    EGA.SYS          Supplementary EGA display driver for real mode; saves and
  888.                     restores state for the EGA write-only control registers
  889.  
  890.    EXTDSKDD.SYS     Creates a new logical drive letter for a block device;
  891.                     optionally configures the medium type and number of
  892.                     heads, tracks, and sectors for the device
  893.  
  894.    KBD01.SYS        Keyboard driver (logical device KBD$) for PC/AT
  895.  
  896.    KBD02.SYS        Keyboard driver (logical device KBD$) for PS/2
  897.  
  898.    MOUSEA00.SYS     Serial interface Mouse Systems PC Mouse driver for PC/AT
  899.                     (logical device MOUSE$)
  900.  
  901.    MOUSEA01.SYS     Serial interface Visi-On Mouse driver for PC/AT (logical
  902.                     device MOUSE$)
  903.  
  904.    MOUSEA02.SYS     Serial interface Microsoft Mouse driver for PC/AT
  905.    Driver           Description
  906.    ──────────────────────────────────────────────────────────────────────────
  907.   MOUSEA02.SYS     Serial interface Microsoft Mouse driver for PC/AT
  908.                     (logical device MOUSE$)
  909.  
  910.    MOUSEA03.SYS     Bus interface Microsoft Mouse driver for PC/AT (logical
  911.                     device MOUSE$)
  912.  
  913.    MOUSEA04.SYS     Driver for Inport Microsoft Mouse for PC/AT (logical
  914.                     device MOUSE$)
  915.  
  916.    MOUSEB00.SYS     Serial interface Mouse Systems PC Mouse driver for PS/2
  917.                     (logical device MOUSE$)
  918.  
  919.    MOUSEB01.SYS     Serial interface Visi-On Mouse driver for PS/2 (logical
  920.                     device MOUSE$)
  921.  
  922.    MOUSEB02.SYS     Serial interface Microsoft Mouse driver for PS/2 (logical
  923.                     device MOUSE$)
  924.  
  925.    MOUSEB05.SYS     Driver for IBM In-board Mouse for PS/2 (logical device
  926.    Driver           Description
  927.    ──────────────────────────────────────────────────────────────────────────
  928.   MOUSEB05.SYS     Driver for IBM In-board Mouse for PS/2 (logical device
  929.                     MOUSE$)
  930.  
  931.    POINTDD.SYS      Pointing device cursor driver (called by mouse drivers,
  932.                     logical device POINTER$)
  933.  
  934.    PRINT01.SYS      Parallel port printer driver (logical devices PRN or
  935.                     LPT1, LPT2, and LPT3) for PC/AT
  936.  
  937.    PRINT02.SYS      Parallel port printer driver (logical devices PRN or
  938.                     LPT1, LPT2, and LPT3) for PS/2
  939.  
  940.    SCREEN01.SYS     Video display driver (logical device SCREEN$) for PC/AT
  941.  
  942.    SCREEN02.SYS     Video display driver (logical device SCREEN$) for PS/2
  943.  
  944.    VDISK.SYS        Electronic or virtual disk (RAM disk) driver
  945.    ──────────────────────────────────────────────────────────────────────────
  946.  
  947.    Driver           Description
  948.    ──────────────────────────────────────────────────────────────────────────
  949. 
  950.  
  951.    Figure 2-3.  Device drivers provided with the OS/2 kernel. Device drivers
  952.    specific to the Presentation Manager are not listed here.
  953.  
  954.    The OS/2 kernel communicates with device drivers through I/O request
  955.    packets; the drivers translate these requests into the proper commands for
  956.    the peripheral device controllers ("adapters"). In IBM PS/2 systems, the
  957.    most primitive parts of the hardware drivers are in ROM (read-only
  958.    memory); these protected mode routines are referred to as the ROM ABIOS
  959.    ("Advanced Basic I/O System") to differentiate them from the more familiar
  960.    real mode ROM BIOS.
  961.  
  962.    Installable drivers will be discussed in more detail under "How OS/2 Is
  963.    Loaded" later in this chapter, and in Chapter 17 (Device Drivers).
  964.  
  965.  The OS/2 Kernel
  966.  
  967.    The kernel implements the fundamental OS/2 services offered to application
  968.    programs. These services include:
  969.  
  970.    ■  File and record management
  971.  
  972.    ■  Character device input and output
  973.  
  974.    ■  Memory management
  975.  
  976.    ■  "Spawning" of other programs
  977.  
  978.    ■  Interprocess communication
  979.  
  980.    ■  Real time clock services including date, time, and programmable timers
  981.  
  982.    ■  MS-DOS emulation
  983.  
  984.    Programs access kernel functions through the OS/2 API by pushing
  985.    parameters on the stack and executing a far call to a named entry point.
  986.    Use of the API is described further in Chapter 3.
  987.  
  988.    The OS/2 kernel is read into memory during system initialization from the
  989.    file OS2DOS.COM on the boot disk.
  990.  
  991.  Dynlink Libraries
  992.  
  993.    Dynamic link libraries (also called dynlink libraries, or DLLs) are
  994.    shareable subroutine libraries that are loaded on demand. In contrast to
  995.    the routines stored in traditional object libraries, which are bound
  996.    permanently into an application's executable file, dynlink library
  997.    routines are bound to an application at its load time. Much of OS/2 is
  998.    distributed in the form of DLLs (see Figure 2-4). In particular, the
  999.    entry points for nearly all of the documented OS/2 kernel API routines are
  1000.    present within eight dynlink libraries: DOSCALL1.DLL, KBDCALLS.DLL,
  1001.    MONCALLS.DLL, MSG.DLL, MOUCALLS.DLL, NLS.DLL, QUECALLS.DLL, and
  1002.    VIOCALLS.DLL. The other dynlink libraries supplied with the kernel, such
  1003.    as SESMGR.DLL or SPOOLCP.DLL, provide specialized functions which normal
  1004.    applications do not need or cannot access.
  1005.  
  1006. ╓┌─┌────────────────┌────────────────────────────────────────────────────────╖
  1007.    Library          Description
  1008.    ──────────────────────────────────────────────────────────────────────────
  1009.    ANSICALL.DLL     Supports ANSI escape sequences for keyboard redefinition
  1010.    Library          Description
  1011.    ──────────────────────────────────────────────────────────────────────────
  1012.    ANSICALL.DLL     Supports ANSI escape sequences for keyboard redefinition
  1013.                     and screen control in protected mode screen groups
  1014.  
  1015.    BKSCALLS.DLL     Basic keyboard subsystem, called by KBDCALLS.DLL
  1016.  
  1017.    BMSCALLS.DLL     Basic mouse subsystem, called by MOUCALLS.DLL
  1018.  
  1019.    BVSCALLS.DLL     Basic video subsystem, called by VIOCALLS.DLL
  1020.  
  1021.    DOSCALL1.DLL     Contains entry points for most of the DOS API functions,
  1022.                     including those for file management, time and date,
  1023.                     memory management, and interprocess communication
  1024.  
  1025.    KBDCALLS.DLL     Keyboard subsystem router, contains entry points for KBD
  1026.                     API functions; based on the caller's screen group,
  1027.                     requests are passed to a keyboard subsystem DLL (such as
  1028.                     BKSCALLS.DLL) for processing
  1029.  
  1030.    MONCALLS.DLL     Contains entry points for the device monitor API
  1031.    Library          Description
  1032.    ──────────────────────────────────────────────────────────────────────────
  1033.   MONCALLS.DLL     Contains entry points for the device monitor API
  1034.  
  1035.    MOUCALLS.DLL     Contains entry points for the MOU API functions; based on
  1036.                     the caller's screen group, requests are passed to a mouse
  1037.                     subsystem DLL (such as BMSCALLS.DLL) for processing
  1038.  
  1039.    MSG.DLL          Contains entry points for message management API
  1040.  
  1041.    NLS.DLL          Contains entry points for internationalization support
  1042.                     API
  1043.  
  1044.    QUECALLS.DLL     Contains entry points for queue management API
  1045.  
  1046.    SESMGR.DLL       Contains entry points for session management API
  1047.  
  1048.    SPOOLCP.DLL      Support for print spooler
  1049.  
  1050.    VIOCALLS.DLL     Video subsystem router, contains entry points for the VIO
  1051.                     API functions; based on the caller's screen group,
  1052.    Library          Description
  1053.    ──────────────────────────────────────────────────────────────────────────
  1054.                    API functions; based on the caller's screen group,
  1055.                     requests are passed to a video subsystem DLL (such as
  1056.                     BVSCALLS.DLL) for processing
  1057.    ──────────────────────────────────────────────────────────────────────────
  1058.  
  1059.  
  1060.    Figure 2-4.  Dynamic link libraries provided with the OS/2 kernel. DLLs
  1061.    specific to the Presentation Manager are not listed here.
  1062.  
  1063.    The code within a dynlink library runs within the context of the calling
  1064.    program and uses its local descriptor table (LDT). Thus, a library always
  1065.    runs in user mode (ring 3) or, in a few cases, with I/O privilege (ring
  1066.    2). Code that must run at ring 0 is always located in the kernel or in
  1067.    device drivers and is entered by a call gate from a DLL or (less commonly)
  1068.    directly from an application. The actual location of the code that
  1069.    performs an API function and the mode in which it executes are invisible
  1070.    to applications and subject to change.
  1071.  
  1072.  The Command Processor
  1073.  
  1074.    The command processor provides a line-oriented user interface to the
  1075.    operating system. It parses and carries out user commands for common
  1076.    services, such as copying, renaming, and deleting files; creating and
  1077.    destroying directories; getting and setting the time and date; listing the
  1078.    contents of directories; and starting programs.
  1079.  
  1080.    The default protected mode command processor is found in the CMD.EXE file.
  1081.    You can be running multiple copies of CMD.EXE simultaneously── one or more
  1082.    in each protected mode session. Because code (text) segments are always
  1083.    shared in OS/2, the actual memory overhead of each additional copy of
  1084.    CMD.EXE is relatively small. CMD.EXE runs as a normal process in user mode
  1085.    (ring 3) and calls documented API services to carry out its functions.
  1086.  
  1087.    The default real mode command processor is implemented in the file
  1088.    COMMAND.COM, just as under MS-DOS. Because the memory that real mode
  1089.    programs can use is a relatively limited resource, COMMAND.COM is divided
  1090.    into three sections──resident, initialization, and transient── each of
  1091.    which guards its memory allocation to a different degree.
  1092.  
  1093.    The resident section is loaded at the low end of the memory allocated for
  1094.    DOS compatibility mode. It contains the routines to process Ctrl-Break and
  1095.    Ctrl-C entries and critical runtime errors for a real mode program, as
  1096.    well as the routines to terminate such programs. It also contains the code
  1097.    to reload the transient portion of COMMAND.COM when necessary.
  1098.  
  1099.    The initialization section of COMMAND.COM is loaded above the resident
  1100.    part when a real mode session starts. This section processes the
  1101.    AUTOEXEC.BAT file (the user's list of initialization commands for a real
  1102.    mode session) and is then discarded.
  1103.  
  1104.    The transient part of COMMAND.COM is loaded at the high end of the memory
  1105.    allocated for compatibility mode, and real mode programs can use its
  1106.    memory for their own purposes. The transient section issues the user
  1107.    prompt, reads commands from the keyboard or batch file, and causes them to
  1108.    be executed. When a real mode program terminates, the resident portion of
  1109.    COMMAND.COM does a checksum of the transient part to determine whether it
  1110.    has been destroyed and fetches a fresh copy from the disk if necessary.
  1111.  
  1112.    The user commands that CMD.EXE and COMMAND.COM accept fall into three
  1113.    categories:
  1114.  
  1115.    ■  Internal commands
  1116.  
  1117.    ■  External commands
  1118.  
  1119.    ■  Batch files
  1120.  
  1121.    Internal commands are those carried out by code that is embedded in the
  1122.    command processor itself. Commands in this category include COPY,
  1123.    REN(AME), DIR(ECTORY), and DEL(ETE). In the case of the real mode
  1124.    processor, COMMAND.COM, the routines for the internal commands are in the
  1125.    transient portion.
  1126.  
  1127.    External commands are simply names of disk files containing executable
  1128.    programs. The term external command usually refers to a program file
  1129.    distributed on the OS/2 operating system disks; in reality, however, OS/2
  1130.    does not distinguish between those programs and any other executable
  1131.    programs when it loads them.
  1132.  
  1133.    A batch file is an ASCII text file that contains a list of internal,
  1134.    external, or batch commands. Batch files are processed by a special
  1135.    interpreter, built into the command processor, that reads the file a line
  1136.    at a time and carries out each of the specified operations in order. Batch
  1137.    files used in the protected mode session have the extension CMD. As under
  1138.    MS-DOS, the extension BAT identifies batch files intended for the real
  1139.    mode command processor, which has its batch file interpreter in the
  1140.    transient portion.
  1141.  
  1142.    From a user perspective, CMD.EXE and COMMAND.COM operate almost
  1143.    identically, although CMD.EXE's internal commands and handling of I/O
  1144.    redirection are somewhat more sophisticated. When the user enters a
  1145.    command, the command processor first checks its list of internal commands
  1146.    to see if it can handle the command directly. If not, the command
  1147.    processor searches the current directory and then each directory named in
  1148.    the environment's PATH string for a file with the same name and the
  1149.    extension COM, EXE, or CMD/BAT (in that order). Of course, if the command
  1150.    processor finds a COM or EXE file, the file must have the appropriate type
  1151.    of header (or none for COM files) for the current CPU mode (real or
  1152.    protected). If the search fails for all file types in all possible
  1153.    locations, the system displays an error message.
  1154.  
  1155.    You can replace both the real and protected mode command processors with
  1156.    alternative, customized command processors by adding or changing a line in
  1157.    the CONFIG.SYS file and restarting the system. Specify the command
  1158.    processor for protected mode with the PROTSHELL directive; use the SHELL
  1159.    directive for the real mode command processor. You can also use these
  1160.    directives to designate locations for the command processors other than
  1161.    the root directory of the system boot disk or to identify a program or
  1162.    batch file to execute when the command processor is first loaded.
  1163.  
  1164.  The Presentation Manager
  1165.  
  1166.    The Presentation Manager is the graphical user interface for OS/2 version
  1167.    1.1; it replaces the character mode Session Manager that was shipped with
  1168.    OS/2 version 1.0 and renders the command processor CMD.EXE largely
  1169.    superfluous. Parts of the Presentation Manager run as normal processes and
  1170.    interact with the user, whereas other parts are distributed among dynlink
  1171.    libraries and provide graphical services to application programs. The
  1172.    Presentation Manager is loaded with a PROTSHELL= statement in the
  1173.    CONFIG.SYS file, which also specifies the protected mode command
  1174.    processor.
  1175.  
  1176.    The three main components of the Presentation Manager's user interface are
  1177.    called Start Programs, Task Manager, and File System. All three run in
  1178.    windows on the Presentation Manager desktop and can be visible
  1179.    simultaneously. The user controls the sizes and positions of the windows,
  1180.    as well as the behavior of the mouse and the colors of the desktop
  1181.    background, text, menus, and title and scroll bars.
  1182.  
  1183.    Start Programs allows the user to start new processes (including command
  1184.    processors) by name from a menu. Well-behaved character-based applications
  1185.    and graphical applications that are written to Presentation Manager
  1186.    conventions run in additional windows, while incompatible applications are
  1187.    run "full-screen" in separate sessions. The Task Manager allows the user
  1188.    to switch between processes and sessions with hot keys or with the mouse.
  1189.    The File System component displays graphical representations of directory
  1190.    trees and lists of directories and files, and it allows files to be
  1191.    copied, moved, renamed, deleted, printed, or selected for execution.
  1192.  
  1193.    The structure and operation of the Presentation Manager is a complex
  1194.    subject that is beyond the scope of this book. For further details, refer
  1195.    to Programming the OS/2 Presentation Manager, by Charles Petzold,
  1196.    published by Microsoft Press.
  1197.  
  1198.  
  1199.  How OS/2 Is Loaded
  1200.  
  1201.    When you power up or reset an 80286 or 80386 CPU, program execution begins
  1202.    in real mode at a predetermined address (FFFF0H for the 80286, FFFFFFF0H
  1203.    for the 80386). This is a hard-wired characteristic of the processor and
  1204.    has nothing to do with OS/2. Computers based on these CPUs are usually
  1205.    designed so that the initial execution address lies within ROM and
  1206.    contains a jump instruction to system test code and the ROM bootstrap
  1207.    routine (Figure 2-5).
  1208.  
  1209.    The ROM bootstrap's job is to read the disk bootstrap into memory from a
  1210.    floppy or fixed disk. The disk bootstrap is a short program that is placed
  1211.    in a disk's boot sector (track 0, head 0, sector 1 for floppy disks) by
  1212.    the FORMAT program. If the read operation is successful, the ROM bootstrap
  1213.    transfers control to the disk bootstrap (Figure 2-6); otherwise, it
  1214.    executes Int 18H to start ROM BASIC.
  1215.  
  1216.     ┌───────────────────────────────┐
  1217.     │                               │ 16 MB
  1218.    ─┴─                             ─┴─
  1219.    ─┬─                             ─┬─
  1220.     │                               │
  1221.     ├───────────────────────────────┤ 1 MB
  1222.     │         ROM bootstrap         │
  1223.     │      ROM BIOS and ABIOS       │
  1224.     │   Adapter memory-mapped I/O   │
  1225.     ├───────────────────────────────┤ 640 KB
  1226.     │                               │
  1227.    ─┴─                             ─┴─
  1228.    ─┬─                             ─┬─
  1229.     │                               │
  1230.     ├───────────────────────────────┤ 1 KB
  1231.     │       Interrupt vectors       │
  1232.     └───────────────────────────────┘ 0 KB
  1233.  
  1234.    Figure 2-5.  Memory map when system is turned on or restarted. The CPU
  1235.    begins execution in real mode at location FFFF0H (80286 systems) or
  1236.    FFFFFFF0H (80386 systems); control passes from there to the ROM bootstrap
  1237.    routine.
  1238.  
  1239.     ┌───────────────────────────────┐
  1240.     │                               │ 16 MB
  1241.    ─┴─                             ─┴─
  1242.    ─┬─                             ─┬─
  1243.     │                               │
  1244.     ├───────────────────────────────┤ 1 MB
  1245.     │         ROM bootstrap         │
  1246.     │      ROM BIOS and ABIOS       │
  1247.     │   Adapter memory-mapped I/O   │
  1248.     ├───────────────────────────────┤ 640 KB
  1249.     │                               │
  1250.    ─┴─                             ─┴─
  1251.    ─┬─                             ─┬─
  1252.     │                               │
  1253.     ├───────────────────────────────┤
  1254.     │        Disk bootstrap         │
  1255.     ├───────────────────────────────┤
  1256.     │                               │
  1257.     ├───────────────────────────────┤ 1 KB
  1258.     │       Interrupt vectors       │
  1259.     └───────────────────────────────┘ 0 KB
  1260.  
  1261.    Figure 2-6.  The ROM bootstrap has read the disk bootstrap into memory
  1262.    from the first sector of the boot drive and given it control. The system
  1263.    is in real mode.
  1264.  
  1265.    The disk bootstrap first checks to see if the boot disk contains a copy of
  1266.    OS/2. It does this by inspecting a table of information (also placed in
  1267.    the boot sector by the FORMAT program) that describes the characteristics
  1268.    of the disk medium, such as the number of tracks, heads, and sectors per
  1269.    track. Using this table, called the BIOS parameter block (BPB), the disk
  1270.    bootstrap can calculate the location of the root directory and then search
  1271.    the directory for the file OS2LDR. If OS2LDR is found, it is read into
  1272.    memory and receives control (Figure 2-7 on the following page).
  1273.    Otherwise, the disk bootstrap displays the following message:
  1274.  
  1275.    The file OS2LDR cannot be found.
  1276.    Insert a system diskette and restart the system.
  1277.  
  1278.    OS2LDR consists almost entirely of a full-fledged loader for segmented
  1279.    executable ("new EXE") files along with a primitive file system built on
  1280.    the ROM BIOS disk driver. OS2LDR first determines the size and location of
  1281.    all contiguous memory below 640 KB and above 1 MB. It then searches the
  1282.    root directory of the boot disk for the file OS2KRNL (which, despite its
  1283.    lack of an extension, is actually a file in the new EXE format) and loads
  1284.    it into memory segment by segment, performing all necessary relocations.
  1285.    Finally, OS2LDR transfers control to OS2KRNL and passes a map of system
  1286.    memory and some other configuration information (Figure 2-8 on page 23).
  1287.  
  1288.     ┌───────────────────────────────┐
  1289.     │                               │ 16 MB
  1290.    ─┴─                             ─┴─
  1291.    ─┬─                             ─┬─
  1292.     │                               │
  1293.     ├───────────────────────────────┤ 1 MB
  1294.     │         ROM bootstrap         │
  1295.     │      ROM BIOS and ABIOS       │
  1296.     │   Adapter memory-mapped I/O   │
  1297.     ├───────────────────────────────┤ 640 KB
  1298.     │                               │
  1299.    ─┴─                             ─┴─
  1300.    ─┬─                             ─┬─
  1301.     │                               │
  1302.     ├───────────────────────────────┤
  1303.     │            OS2LDR             │
  1304.     ├───────────────────────────────┤
  1305.     │                               │
  1306.     ├───────────────────────────────┤
  1307.     │     Spent disk bootstrap      │
  1308.     ├───────────────────────────────┤
  1309.     │                               │
  1310.     ├───────────────────────────────┤ 1 KB
  1311.     │       Interrupt vectors       │
  1312.     └───────────────────────────────┘ 0 KB
  1313.  
  1314.    Figure 2-7.  The disk bootstrap has read the OS2LDR file into memory and
  1315.    given it control. The memory occupied by the disk bootstrap program will
  1316.    be overlaid by other OS/2 modules. The system is still in real mode.
  1317.  
  1318.    The OS2KRNL file is made up of many code and data segments containing the
  1319.    vital modules of the OS/2 kernel. The system initialization module
  1320.    (SysInit) receives control when OS2KRNL is first entered. SysInit's first
  1321.    job is to establish protected mode operation: It creates the necessary
  1322.    global descriptor table (GDT), interrupt descriptor table (IDT), task
  1323.    state segment (TSS), and so on, after which it sets the PE bit in the
  1324.    machine status word (CR0) to switch modes. Creation of a valid IDT permits
  1325.    the system to initialize the two 8259 programmable interrupt controllers
  1326.    (PICs), which enable and service interrupts.
  1327.  
  1328.    SysInit then proceeds with the remaining CPU and memory initialization by
  1329.    moving the various kernel modules to their final locations. To provide a
  1330.    maximum amount of memory for real mode operations, it leaves only the most
  1331.    critical kernel components, such as the scheduler, in the 0─640 KB region
  1332.    ("System Low"); the remaining components, such as the file system and
  1333.    signal handlers, are placed above the 1 MB boundary ("System High"). It
  1334.    then initializes the physical memory manager (PMM), virtual memory manager
  1335.    (VMM), and the system memory arena (the area of dynamically allocatable
  1336.    memory).
  1337.  
  1338.     ┌───────────────────────────────┐
  1339.     │                               │ 16 MB
  1340.    ─┴─                             ─┴─
  1341.    ─┬─                             ─┬─
  1342.     │                               │
  1343.     ├───────────────────────────────┤ 1 MB
  1344.     │         ROM bootstrap         │
  1345.     │      ROM BIOS and ABIOS       │
  1346.     │   Adapter memory-mapped I/O   │
  1347.     ├───────────────────────────────┤ 640 KB
  1348.     │                               │
  1349.     ├───────────────────────────────┤
  1350.     │                               │
  1351.     │            OS2KRNL            │
  1352.     │                               │
  1353.     ├───────────────────────────────┤
  1354.     │                               │
  1355.     ├───────────────────────────────┤
  1356.     │            OS2LDR             │
  1357.     ├───────────────────────────────┤
  1358.     │                               │
  1359.     ├───────────────────────────────┤ 1 KB
  1360.     │       Interrupt vectors       │
  1361.     └───────────────────────────────┘ 0 KB
  1362.  
  1363.    Figure 2-8.  OS2LDR, which contains a full-fledged loader for segmented
  1364.    executable files, has brought OS2KRNL into memory. OS2KRNL contains the
  1365.    critical portions of the OS/2 kernel, including the physical and virtual
  1366.    memory managers, the scheduler, and the file system. When control passes
  1367.    to OS2KRNL, the system is switched from real mode to protected mode, and
  1368.    the memory occupied by OS2LDR can be reused.
  1369.  
  1370.    Next, SysInit reads the CONFIG.SYS file into memory from the root
  1371.    directory of the boot disk. This file contains directives that control
  1372.    various aspects of the kernel's operation. Based on the detected hardware
  1373.    configuration, SysInit then loads the base set of device drivers: For a
  1374.    PC/AT or compatible, these are DISK01.SYS (fixed disk and floppy disk),
  1375.    KBD01.SYS (keyboard), SCREEN01.SYS (video display), CLOCK01.SYS (real time
  1376.    clock), and PRINT01.SYS (parallel port). The system file table and disk
  1377.    buffers are then allocated, and the file system is initialized. As under
  1378.    MS-DOS, the number of disk buffers is controlled by a BUFFERS= statement
  1379.    in CONFIG.SYS. The system file table, however, can grow dynamically; any
  1380.    FILES= statement in CONFIG.SYS is ignored.
  1381.  
  1382.    Until this point in the loading sequence, SysInit has been running
  1383.    autonomously at the highest privilege level (ring 0). SysInit brings the
  1384.    OS/2 kernel to life by calling a special scheduler entry point, which
  1385.    causes the scheduler to allocate memory for its dispatch table and then
  1386.    initialize it. Upon return from the scheduler, SysInit finds itself
  1387.    running as a normal process in user mode (ring 3).
  1388.  
  1389.    Most of the remaining SysInit tasks are carried out through documented API
  1390.    services. Because SysInit was not brought into memory by the system
  1391.    loader, which ordinarily resolves dynamic links to API entry points on
  1392.    behalf of processes, SysInit must use DosLoadModule and DosGetProcAddr
  1393.    (discussed in Chapter 19) to establish links to the API functions it
  1394.    requires.
  1395.  
  1396.    SysInit also uses DosLoadModule to load optional device drivers specified
  1397.    by DEVICE directives in the CONFIG.SYS file. A special kernel entry point
  1398.    provided for SysInit lets it request that the kernel run the driver's Init
  1399.    routine after the driver is loaded. During its Init procedure, the driver
  1400.    allocates any additional fixed memory it needs, initializes its adapter,
  1401.    and captures the necessary interrupt vectors.
  1402.  
  1403.    Similarly, SysInit uses DosExecPgm to launch any background, or "daemon,"
  1404.    processes specified with RUN directives in the CONFIG.SYS file. Such
  1405.    processes are placed in a "black box" and must use special popup video
  1406.    calls if they need to interact with the user. Finally, SysInit loads and
  1407.    executes the Presentation Manager shell program named in the PROTSHELL
  1408.    directive in CONFIG.SYS and then calls DosExit to terminate. The memory
  1409.    SysInit has occupied is then reclaimed like that of any other process.
  1410.  
  1411.    The Presentation Manager shell switches the system into graphics mode and
  1412.    displays the desktop with the Task Manager, DOS compatibility box, and
  1413.    Print Spooler icons. It then inspects the root directory of the boot disk
  1414.    for a batch file named STARTUP.CMD. If the file exists, a copy of CMD.EXE
  1415.    is started in a window to carry out the commands in the file. Finally, the
  1416.    Program Starter menu is displayed, and the system awaits a command
  1417.    from the user.
  1418.  
  1419.    Figures 2-9 and 2-10 illustrate two possible configurations that result
  1420.    from the OS/2 boot process. The two configurations are basically the same
  1421.    except for the way they use lower memory. When DOS compatibility mode is
  1422.    enabled (Figure 2-9), the storage below the boundary specified by the
  1423.    RMSIZE directive in CONFIG.SYS (or 640 KB, if RMSIZE is absent) is
  1424.    reserved for COMMAND.COM and real mode applications──apart from the area
  1425.    occupied by OS/2 "System Low" and the device drivers, of course. Protected
  1426.    mode programs (Figure 2-10) can still use any memory below 640 KB that is
  1427.    above the boundary specified by the RMSIZE directive.
  1428.  
  1429.                     ┌───────────────────────────────┐ Top of installed
  1430.                     │   Fixed allocatable memory    │  extended memory
  1431.                   ┌─├───────────────────────────────┤
  1432.    Protected mode │ │                               │
  1433.    memory arena  ─┤ │        Protected mode         │
  1434.    (Segments can  │ │         applications          │
  1435.    be moved and   │ │                               │
  1436.    swapped.)      └─├───────────────────────────────┤
  1437.                     │       System file table       │
  1438.                     ├───────────────────────────────┤
  1439.                     │         Disk buffers          │
  1440.                     ├───────────────────────────────┤
  1441.                     │         "System High"         │
  1442.                     │          OS/2 kernel          │
  1443.                     ├───────────────────────────────┤  1 MB
  1444.                     │         ROM bootstrap         │
  1445.                     │      ROM BIOS and ABIOS       │
  1446.                     │   Adapter memory-mapped I/O   │
  1447.                   ┌─├───────────────────────────────┤  640 KB
  1448.    Protected mode │ │                               │
  1449.    memory arena  ─┤ │        Protected mode         │
  1450.    (Segments can  │ │         applications          │
  1451.    be moved and   │ │                               │
  1452.    swapped.)      ╞═├───────────────────────────────┤ RMSIZE
  1453.                   │ │     Transient COMMAND.COM     │
  1454.    DOS com-       │ ├───────────────────────────────┤
  1455.    patibility    ─┤ │    Transient program area     │
  1456.    mode memory    │ │         for real mode         │
  1457.    arena          │ ├───────────────────────────────┤
  1458.                   │ │     Resident COMMAND.COM      │
  1459.                   └─├───────────────────────────────┤
  1460.                     │   Real mode device drivers    │
  1461.                     ├───────────────────────────────┤
  1462.                     │   Dual mode device drivers    │
  1463.                     ├───────────────────────────────┤
  1464.                     │         "System Low"          │
  1465.                     │          OS/2 kernel          │
  1466.                     ├───────────────────────────────┤  1 KB
  1467.                     │       Interrupt vectors       │
  1468.                     └───────────────────────────────┘  0 KB
  1469.  
  1470.    Figure 2-9.  Final memory map for OS/2 systems supporting both protected
  1471.    mode and real mode applications (PROTECTONLY=NO). Depending on the RMSIZE
  1472.    directive in CONFIG.SYS, the memory arena for DOS compatibility mode can
  1473.    occupy all or only part of the memory below 640 KB that is not used by
  1474.    OS/2 "System Low" and its device drivers.
  1475.  
  1476.                     ┌───────────────────────────────┐ Top of installed
  1477.                     │   Fixed allocatable memory    │  extended memory
  1478.                   ┌─├───────────────────────────────┤
  1479.    Protected mode │ │                               │
  1480.    memory arena  ─┤ │        Protected mode         │
  1481.    (Segments can  │ │         applications          │
  1482.    be moved and   │ │                               │
  1483.    swapped.)      └─├───────────────────────────────┤
  1484.                     │       System file table       │
  1485.                     ├───────────────────────────────┤
  1486.                     │         Disk buffers          │
  1487.                     ├───────────────────────────────┤
  1488.                     │         "System High"         │
  1489.                     │          OS/2 kernel          │
  1490.                     ├───────────────────────────────┤  1 MB
  1491.                     │         ROM bootstrap         │
  1492.                     │      ROM BIOS and ABIOS       │
  1493.                     │   Adapter memory-mapped I/O   │
  1494.                   ┌─├───────────────────────────────┤  640 KB
  1495.    Memory arena   │ │                               │
  1496.    (Segments can ─┤ │        Protected mode         │
  1497.    be moved and   │ │         applications          │
  1498.    swapped.)      │ │                               │
  1499.                   └─├───────────────────────────────┤
  1500.                     │        Device drivers         │
  1501.                     ├───────────────────────────────┤
  1502.                     │         "System Low"          │
  1503.                     │          OS/2 kernel          │
  1504.                     └───────────────────────────────┘  0 KB
  1505.  
  1506.    Figure 2-10.  Final memory map for OS/2 systems supporting protected mode
  1507.    applications only (PROTECTONLY=YES in CONFIG.SYS). Note that in a
  1508.    protected-mode-only system the interrupt vector table is not fixed at
  1509.    location 0 but can float anywhere in memory.
  1510.  
  1511.  
  1512.  
  1513.  ────────────────────────────────────────────────────────────────────────────
  1514.  Chapter 3  OS/2 Application Programs
  1515.  
  1516.    OS/2 is both a platform for a new generation of multitasking graphical
  1517.    applications and a bridge from the 640 KB real mode MS-DOS environment.
  1518.    Consequently, you can write new applications for OS/2 at several different
  1519.    levels of complexity:
  1520.  
  1521.    ■  Kernel applications
  1522.  
  1523.    ■  Family applications
  1524.  
  1525.    ■  Presentation Manager applications
  1526.  
  1527.    Kernel applications execute only in protected mode and use the OS/2 kernel
  1528.    services for keyboard, screen, and mouse I/O rather than using those
  1529.    offered by the Presentation Manager (PM). They have full access to OS/2's
  1530.    multitasking, virtual memory, interprocess communication, and asynchronous
  1531.    I/O capabilities. Although Kernel applications can run in a window under
  1532.    the Presentation Manager, they are ordinarily limited to
  1533.    character-oriented displays. (If a program includes its own graphics
  1534.    drivers, it gives up the ability to run in a window.)
  1535.  
  1536.    Family applications are built like Kernel applications; however, they use
  1537.    only those OS/2 functions that have direct counterparts in MS-DOS, and
  1538.    they do not contain any machine instructions that are unique to the 80286
  1539.    or 80386. A Family application goes through an extra linkage step which
  1540.    "binds" it to code that intercepts OS/2 API calls, if the program is
  1541.    loaded in real mode, and converts them to MS-DOS Int 21H calls. Such a
  1542.    "bound" program can run in either protected mode or real mode under OS/2
  1543.    or MS-DOS on any 8086/88, 80286, or 80386-based machine. Many Microsoft
  1544.    programming tools are written as Family applications so that they can be
  1545.    used to cross-compile OS/2 programs under MS-DOS or vice versa.
  1546.  
  1547.    Presentation Manager applications have a radically different internal
  1548.    structure and flow of control than do Family or Kernel applications. The
  1549.    "main" routine is a simple loop that reads a message off an input queue,
  1550.    optionally translates the message, and then redispatches the message to a
  1551.    relatively autonomous routine, known as a window procedure, that is
  1552.    associated with a specific screen region. The message might consist of a
  1553.    keypress, a key release, a mouse movement, a signal from the system to
  1554.    repaint part of a window, a notification that the application has been
  1555.    "iconized," and so forth. Presentation Manager applications have full
  1556.    access to the powerful PM library of device-independent graphics services.
  1557.  
  1558.    The remainder of this chapter, and indeed most of the remainder of this
  1559.    book, concerns itself only with Kernel applications: their structure in
  1560.    source code, on disk, and in memory, and the way in which they are loaded,
  1561.    communicate with the operating system and other processes, obtain system
  1562.    resources such as files and additional memory, and terminate.
  1563.  
  1564.  
  1565.  OS/2 Program Source Structure
  1566.  
  1567.    An OS/2 Kernel application is built from two basic types of text files:
  1568.    one or more language-specific source files that can be compiled or
  1569.    assembled into relocatable object modules, and a module definition file
  1570.    that describes the program's segment characteristics. This chapter
  1571.    confines itself to a discussion of MASM applications, although most of the
  1572.    principles involved apply to high level languages as well.
  1573.  
  1574.    An OS/2 assembly language program has a well-defined structure at two
  1575.    levels: the level of segments and that of procedures. Segments are
  1576.    physical groupings of like items within a program and an enforced
  1577.    separation of dissimilar items; procedures are functional subdivisions of
  1578.    the executable program. Figure 3-1 contains a segmentation skeleton of an
  1579.    OS/2 assembly language program.
  1580.  
  1581.    In 80286 protected mode, a memory segment occupies from 1 to 65,536 bytes
  1582.    and carries one of three attributes: executable code (text), read/write
  1583.    data, or read-only (static) data. An executable selector cannot be used to
  1584.    write to its segment; code segments are sometimes described as "pure"
  1585.    because under normal circumstances they are not modifiable. (OS/2 does
  1586.    provide a way to obtain executable and data selectors that refer to the
  1587.    same physical memory segment; this method will be discussed in Chapter
  1588.    11.)
  1589.  
  1590.    ──────────────────────────────────────────────────────────────────────────
  1591.            title   MYPROG -- Segmentation Skeleton
  1592.            page    55,132
  1593.            .286                    ; allow 80286 instructions
  1594.  
  1595.            .                       ; miscellaneous equates,
  1596.            .                       ; structures, and other
  1597.            .                       ; declarations go here
  1598.  
  1599.            extrn   DosExit:far     ; system services used by
  1600.                                    ; program are declared here
  1601.  
  1602.    DGROUP  group   _DATA           ; declare 'automatic data
  1603.                                    ; group' for OS/2 loader
  1604.  
  1605.    _DATA   segment word public 'DATA'
  1606.  
  1607.            .                       ; all variable and constant
  1608.            .                       ; data goes in this segment
  1609.            .
  1610.  
  1611.    _DATA   ends
  1612.  
  1613.    _TEXT   segment word public 'CODE'
  1614.  
  1615.            assume  cs:_TEXT,ds:DGROUP
  1616.  
  1617.    main    proc    far             ; routine which initially
  1618.            .                       ; receives control can be
  1619.            .                       ; called anything...
  1620.            .
  1621.            push    1               ; main routine terminates
  1622.            push    0               ; all threads and passes a
  1623.            call    DosExit         ; return code to OS/2
  1624.  
  1625.    main    endp
  1626.  
  1627.            .                       ; other routines needed by
  1628.            .                       ; the program go here
  1629.            .
  1630.  
  1631.    _TEXT   ends
  1632.  
  1633.            end     main            ; declares end of module
  1634.                                    ; and initial entry point
  1635.    ──────────────────────────────────────────────────────────────────────────
  1636.  
  1637.    Figure 3-1.  Skeleton of a properly segmented assembly language
  1638.    application for OS/2.
  1639.  
  1640.    The 80286's insistence on separation of executable code and data in
  1641.    protected mode dictates that every program that runs under OS/2, including
  1642.    assembly language programs, must have a minimum of two segments: a code
  1643.    segment and a data segment. The "near data" segment, or the segment that
  1644.    the program expects to address with offsets from the DS register, should
  1645.    be named as a component of DGROUP with the group directive. DGROUP is
  1646.    known as the "automatic data segment" and receives special treatment by
  1647.    the OS/2 loader.
  1648.  
  1649.    Programs are classified into one memory model or another by the number of
  1650.    their code and data segments. The most commonly used memory model for
  1651.    assembly language programs is the small model, which has one code and one
  1652.    data segment, but several others may also be used (Figure 3-2). For each
  1653.    model, Microsoft has established certain segment and class names that are
  1654.    used by all its high level language compilers (Figure 3-3). I recommend
  1655.    that you follow these conventions in assembly language programming too,
  1656.    because they make it easier to integrate routines from high level language
  1657.    (HLL) libraries into your assembly programs or to use your assembly
  1658.    language subroutines in HLL programs.
  1659.  
  1660.    The program must have a primary procedure that receives control from OS/2.
  1661.    To specify the entry point, include the procedure name in the END
  1662.    statement at the end of the file. The main procedure's attribute (near or
  1663.    far) is not particularly important because a program terminates its
  1664.    execution by a DosExit function call rather than by a subroutine RETurn
  1665.    instruction. By convention, people assign the far attribute to the main
  1666.    procedure. (OS/2 function calls are discussed later in this chapter.)
  1667.  
  1668.    Other procedures are assigned a near or far attribute depending on the
  1669.    memory model and depending on whether they are called only from the same
  1670.    segment or are the target of intersegment calls. Keep procedures reentrant
  1671.    whenever possible so that they can be shared between concurrent threads
  1672.    (subprocesses) without any special synchronization. If you are coding in
  1673.    C, procedures are almost naturally reentrant unless you declare variables
  1674.    static; if you are coding in assembly language, the 80286's enter and
  1675.    leave instructions allow the easy implementation of local, automatic
  1676.    variables for subroutines.
  1677.  
  1678.    Model                    Code Segments           Data Segments
  1679.    ──────────────────────────────────────────────────────────────────────────
  1680.    Small                    One                     One
  1681.    Medium                   Multiple                One
  1682.    Compact                  One                     Multiple
  1683.    Large                    Multiple                Multiple
  1684.    ──────────────────────────────────────────────────────────────────────────
  1685.  
  1686.    Figure 3-2.  Terminology for memory models commonly used in assembly
  1687.    language and C programs. Two additional models exist that are not relevant
  1688.    to this book: the tiny model, which consists of intermixed code and data
  1689.    in a single segment (for example, a COM file under MS-DOS); and the huge
  1690.    model, which is supported by the Microsoft C Compiler and allows use of
  1691.    data structures larger than 64 KB.
  1692.  
  1693. ╓┌─┌─────────┌─────────────┌─────────┌──────────┌─────────┌──────────────────╖
  1694.    Memory    Segment       Align     Combine    Class
  1695.    Model     Name          Type      Class      Name      Group
  1696.    ──────────────────────────────────────────────────────────────────────────
  1697.    Small     _TEXT         word      public     CODE
  1698.              _DATA         word      public     DATA      DGROUP
  1699.              STACK         para      stack      STACK     DGROUP
  1700.  
  1701.    Medium    module_TEXT   word      public     CODE
  1702.              .
  1703.    Memory    Segment       Align     Combine    Class
  1704.    Model     Name          Type      Class      Name      Group
  1705.    ──────────────────────────────────────────────────────────────────────────
  1706.             .
  1707.              .
  1708.              .
  1709.              _DATA         word      public     DATA      DGROUP
  1710.              STACK         para      stack      STACK     DGROUP
  1711.  
  1712.    Compact   _TEXT         word      public     CODE
  1713.              data          para      private    FAR_DATA
  1714.              .
  1715.              .
  1716.              .
  1717.              _DATA         word      public     DATA      DGROUP
  1718.              STACK         para      stack      STACK     DGROUP
  1719.  
  1720.    Large     module_TEXT   word      public     CODE
  1721.              .
  1722.              .
  1723.              .
  1724.    Memory    Segment       Align     Combine    Class
  1725.    Model     Name          Type      Class      Name      Group
  1726.    ──────────────────────────────────────────────────────────────────────────
  1727.             .
  1728.              data          para      private    FAR_DATA
  1729.              .
  1730.              .
  1731.              .
  1732.              _DATA         word      public     DATA      DGROUP
  1733.              STACK         para      stack      STACK     DGROUP
  1734.    ──────────────────────────────────────────────────────────────────────────
  1735.  
  1736.  
  1737.    Figure 3-3.  Segments, groups, and classes for the standard memory models
  1738.    as used with assembly language programs. Microsoft C programs use a
  1739.    superset of these segments and classes.
  1740.  
  1741.  
  1742.  Module Definition Files
  1743.  
  1744.    A module definition file (with the extension DEF) describes the segment
  1745.    characteristics of an OS/2 application program or dynamic link library.
  1746.    The major elements of a definition file are the following:
  1747.  
  1748.    ■  The executable program or dynlink library name, defined with the NAME
  1749.       or LIBRARY directive, respectively
  1750.  
  1751.    ■  The initial size of the local heap for C programs, defined with the
  1752.       HEAPSIZE directive (The heap can be expanded automatically as needed at
  1753.       run time, as long as the total of the program's data, stack, and local
  1754.       heap does not exceed 65,536 bytes.)
  1755.  
  1756.    ■  The stack size, defined with the STACKSIZE directive
  1757.  
  1758.    ■  The characteristics of the code and data segments, defined with the
  1759.       CODE and DATA directives along with suitable modifiers
  1760.  
  1761.    ■  The names of imported (dynamically linked) or exported routines
  1762.  
  1763.    Other DEF file directives determine whether a code segment has I/O
  1764.    privilege (IOPL), place descriptive strings into a load module for
  1765.    documentation purposes, or specify the filename of an MS-DOS─compatible
  1766.    stub program which will be merged into the new executable file and receive
  1767.    control if it is loaded in real mode.
  1768.  
  1769.    Module definition files are also used by the IMPLIB utility to create
  1770.    import libraries──special object module libraries that contain reference
  1771.    records for the entry points in dynlink libraries. When the Linker
  1772.    resolves an extrn reference in an application against a reference record,
  1773.    an IMPORTS statement in the module definition file is not necessary. Thus,
  1774.    import libraries simplify the application building process; they are
  1775.    discussed further in Chapter 19.
  1776.  
  1777.    Figure 3-4 contains a summary of module definition file syntax. For more
  1778.    detailed information, refer to Appendix E.
  1779.  
  1780.    NAME [ module name ]
  1781.         [ WINDOWAPI | WINDOWCOMPAT | NOTWINDOWCOMPAT ]
  1782.    DESCRIPTION 'string'
  1783.    HEAPSIZE bytes
  1784.    STACKSIZE bytes
  1785.    PROTMODE | REALMODE
  1786.    EXETYPE [ OS2 | WINDOWS | DOS4 ]
  1787.  
  1788.    CODE [ PRELOAD | LOADONCALL ][ EXECUTEONLY | EXECUTEREAD ]
  1789.      │  [ IOPL | NOIOPL ][ CONFORMING | NONCONFORMING ]
  1790.      │  ┌─────────────────────────────────────────────────────────────┐
  1791.      └──┤ If no CODE statement is present, the default attributes are │
  1792.         │ LOADONCALL EXECUTEREAD NOIOPL NONCONFORMING                 │
  1793.         └─────────────────────────────────────────────────────────────┘
  1794.    DATA [ PRELOAD | LOADNONCALL ][ READONLY | READWRITE ]
  1795.      │  [ NONE | SINGLE | MULTIPLE ][ SHARED | NONSHARED ]
  1796.      │  [ IOPL | NOIOPL ]
  1797.      │  ┌─────────────────────────────────────────────────────┐
  1798.      └──┤ If no DATA statement is present, the attributes are │
  1799.         │ LOADONCALL READWRITE MULTIPLE NONSHARED NOIOPL      │
  1800.         └─────────────────────────────────────────────────────┘
  1801.    SEGMENTS
  1802.         name [ CLASS ['classname']][ PRELOAD | LOADONCALL ]
  1803.              [ READONLY | READWRITE ]
  1804.              [ EXECUTEONLY | EXECUTEREAD ][ IOPL | NOIOPL ]
  1805.              [ CONFORMING | NONCONFORMING ]
  1806.              [ SHARED | NONSHARED ]
  1807.              .                 ┌─────────────────────────────────────┐
  1808.              .                 │ Number of stack parameters (words); │
  1809.                           ┌────┤ required only for IOPL segments     │
  1810.                           │    └─────────────────────────────────────┘
  1811.    EXPORTS         ┌──────┴──────┐
  1812.         exportname [ stackparams ]
  1813.              .
  1814.              .
  1815.    IMPORTS
  1816.         [ internalname = ] modulename.entryname | modulename.ordinal
  1817.              .
  1818.              .
  1819.    STUB 'filename.EXE'
  1820.  
  1821.    Figure 3-4.  Syntax summary of a module definition file for OS/2
  1822.    applications. Defaults are in boldface. Keywords must always be entered in
  1823.    uppercase letters. A similar syntax summary for drivers and dynlink
  1824.    libraries appears in Chapter 19.
  1825.  
  1826.  
  1827.  OS/2 Program Files
  1828.  
  1829.    Source code files are first translated to relocatable object modules,
  1830.    which contain machine code and symbolic information, by an assembler or
  1831.    compiler. Object modules are not suitable for execution without further
  1832.    processing. For easier management they are often collected together into
  1833.    object module libraries.
  1834.  
  1835.    Object modules are converted into executable programs (with the aid of
  1836.    module definition files, import libraries, and object module libraries) by
  1837.    a utility called the Segmented Executable Linker. The executable programs
  1838.    then reside on the disk as load modules called "segmented executable"
  1839.    files, or "new EXE" files. The format of a segmented executable file is
  1840.    identical to that of the load modules used by Microsoft Windows and is a
  1841.    superset of the "old EXE" format of MS-DOS. It has the following
  1842.    components:
  1843.  
  1844.    ■  A header describing the program's structure
  1845.  
  1846.    ■  A "stub" program for real mode
  1847.  
  1848.    ■  At least one code segment
  1849.  
  1850.    ■  At least one data segment
  1851.  
  1852.    The header contains, among other elements, a "signature" (identifying the
  1853.    type of load module), a checksum, the program entry point, information
  1854.    about the code and data segments in the file, and a list of external
  1855.    routines to be dynamically linked at load time (also called imported
  1856.    routines). The stub program is executed if the program is invoked in real
  1857.    mode (either in DOS compatibility mode or under MS-DOS). The default stub
  1858.    program is a short routine inserted by the Linker that displays the
  1859.    message This program cannot run in DOS mode and terminates.
  1860.  
  1861.    Each segment in the file has an attribute (code, read-only data, or
  1862.    read/write data), its own relocation table, and flags indicating whether
  1863.    it should be preloaded or loaded on demand. Code segments are always
  1864.    read-only ("pure") and can be shared among multiple instances of the same
  1865.    program.
  1866.  
  1867.    Theoretically, the size of an OS/2 program is limited only by the number
  1868.    of symbols and segments that the Linker can process. Practically speaking,
  1869.    program size is limited by the amount of disk space available to hold the
  1870.    executable file, the virtual memory manager's swap file, and the number of
  1871.    available LDT selectors──this still allows for some pretty large programs!
  1872.  
  1873.    A block diagram of a segmented EXE file can be found in Figure 3-5. See
  1874.    Appendix D for a more detailed description of the file format.
  1875.  
  1876.         ┌──────────────────────────────────────────┐
  1877.    00H  │   MS-DOS-compatible ("old EXE") header   │
  1878.         ├──────────────────────────────────────────┤
  1879.    20H  │                 Reserved                 │
  1880.         ├──────────────────────────────────────────┤
  1881.    3CH  │        Offset to "new EXE" header        │
  1882.         ├──────────────────────────────────────────┤
  1883.    40H  │ MS-DOS stub program and relocation table │
  1884.         ├──────────────────────────────────────────┤
  1885.         │              New EXE header              │
  1886.         ├──────────────────────────────────────────┤
  1887.         │              Segment table               │
  1888.         ├──────────────────────────────────────────┤
  1889.         │              Resource table              │
  1890.         ├──────────────────────────────────────────┤
  1891.         │           Resident names table           │
  1892.         ├──────────────────────────────────────────┤
  1893.         │          Module reference table          │
  1894.         ├──────────────────────────────────────────┤
  1895.         │           Imported names table           │
  1896.         ├──────────────────────────────────────────┤
  1897.         │               Entry table                │
  1898.         ├──────────────────────────────────────────┤
  1899.         │         Nonresident names table          │
  1900.         ├──────────────────────────────────────────┤
  1901.         │     Segment #1 program code or data      │
  1902.         │    Segment #1 relocation information     │
  1903.         ├──────────────────────────────────────────┤
  1904.        ─┴─                                        ─┴─
  1905.        ─┬─                                        ─┬─
  1906.         ├──────────────────────────────────────────┤
  1907.         │     Segment #n program code or data      │
  1908.         │    Segment #n relocation information     │
  1909.         └──────────────────────────────────────────┘
  1910.  
  1911.    Figure 3-5.  Block diagram of the "new EXE" format for executable program
  1912.    files under OS/2. In Presentation Manager applications, "resources" (such
  1913.    as icons, bitmaps, string tables, and menus) follow the last code or data
  1914.    segment.
  1915.  
  1916.  
  1917.  Program Loading
  1918.  
  1919.    A general-purpose mechanism called the system loader brings programs into
  1920.    memory and prepares them for execution. Command processors and other
  1921.    programs invoke the loader by calling the OS/2 function DosExecPgm
  1922.    (described in more detail in Chapter 12).
  1923.  
  1924.    When the loader is called upon to load a program, it inspects the EXE file
  1925.    header and allocates sufficient memory to hold the segments in the program
  1926.    that are marked "preload." These typically include at least one segment
  1927.    holding machine code (text), a segment for the program's data and stack,
  1928.    and a segment for the environment, program name, and command line
  1929.    information (Figure 3-6 on the following page).
  1930.  
  1931.    ┌──────────────────────────┐
  1932.    │ Command line information │
  1933.    ├──────────────────────────┤ AX:BX
  1934.    │       Program name       │
  1935.    ├──────────────────────────┤
  1936.    │    Environment block     │
  1937.    └──────────────────────────┘ AX:0000H
  1938.    ┌──────────────────────────┐             ──┐
  1939.    │        Local heap        │               │
  1940.    │      (if C program)      │               │
  1941.    ├──────────────────────────┤ SS:SP         │
  1942.    │          Stack           │               │
  1943.    │            │             │               ├── DGROUP
  1944.    │                         │               │
  1945.    ├──────────────────────────┤               │
  1946.    │    Near data segment     │               │
  1947.    │         (_DATA)          │               │
  1948.    └──────────────────────────┘ SS=DS:0000H ──┘
  1949.    ┌──────────────────────────┐
  1950.    │       Program code       │
  1951.    │         (_TEXT)          │
  1952.    └──────────────────────────┘ CS:0000H
  1953.  
  1954.    Figure 3-6.  Memory image of a typical "small model" MASM or C program
  1955.    immediately after loading. The program's data, stack, and local heap
  1956.    reside in the same physical segment, called DGROUP. The segments need not
  1957.    be arranged in the order shown here; in fact, the physical location is not
  1958.    visible to the program itself. The entry point can be anywhere in the code
  1959.    segment and is specified by an end statement in one of the source modules
  1960.    of the program.
  1961.  
  1962.    The loader also performs a memory overcommit calculation to make sure that
  1963.    enough physical memory and swap file space will be available at any point
  1964.    during the program's execution. The loader's attempts to allocate memory
  1965.    for a new program might cause the memory manager to move, swap, or discard
  1966.    segments belonging to other programs.
  1967.  
  1968.    As each segment is read into memory from the EXE file, the loader performs
  1969.    any necessary selector fixups as specified by the segment's associated
  1970.    relocation table. Segments that are marked "load on demand" are assigned a
  1971.    selector but are not initially brought into physical memory. The loader
  1972.    also performs any loadtime dynamic linking that may be required, fixing up
  1973.    the addresses in far calls to OS/2 system services or to
  1974.    application-specific dynlink libraries.
  1975.  
  1976.    Once the essential parts of a program have been loaded into memory and
  1977.    relocated, any dynamic links have been resolved, and the program has been
  1978.    added to the list of jobs maintained by the kernel's scheduler, the
  1979.    program becomes a process.
  1980.  
  1981.  
  1982.  Program Entry Conditions
  1983.  
  1984.    The initial values of the code segment (CS) and instruction pointer (IP)
  1985.    registers are calculated from the entry point information in the EXE file
  1986.    header. The data segment (DS) and stack segment (SS) registers are
  1987.    initialized with the segment selector for DGROUP, which contains the
  1988.    program's near data, stack, and (for C programs) local heap. The stack
  1989.    pointer (SP) is loaded with the offset of the base of the program's stack,
  1990.    while the AX and BX registers are set up with a selector and offset that
  1991.    provide access to the program's environment block, filename, and command
  1992.    line. Other registers contain less vital information, such as the stack
  1993.    size and heap size (Figure 3-7).
  1994.  
  1995.    Register                  Contents
  1996.    ──────────────────────────────────────────────────────────────────────────
  1997.    CS:IP                     Initial entry point
  1998.    SS:SP                     Base of default stack
  1999.    DS                        Segment selector for DGROUP
  2000.    ES                        Zero
  2001.    AX                        Segment selector of environment
  2002.    BX                        Offset of command line information following
  2003.                              environment
  2004.    CX                        Initial size of DGROUP (0 = 65,536 bytes)
  2005.    DX                        Initial size of default stack
  2006.    SI                        Initial size of local heap (C programs)
  2007.    DI                        Module handle for the process
  2008.    BP                        Zero
  2009.    ──────────────────────────────────────────────────────────────────────────
  2010.  
  2011.    Figure 3-7.  Register contents at entry to a protected mode OS/2
  2012.    application.
  2013.  
  2014.    The environment block for a particular process is inherited from the
  2015.    process's parent. It consists of a series of null-terminated ASCII
  2016.    (ASCIIZ) strings, called environment variables, of the following form:
  2017.  
  2018.      NAME=PARAMETER
  2019.  
  2020.    Variables are placed in the environment block as a result of certain
  2021.    directives in the CONFIG.SYS file, and by the command processor as a
  2022.    result of PROMPT, PATH, and SET commands (Figure 3-8 on the following
  2023.    page). When multiple sessions are active, the command processor in each
  2024.    session maintains a "master environment" for that session.
  2025.  
  2026.    ──────────────────────────────────────────────────────────────────────────
  2027.    COMSPEC=C:\OS2\PBIN\CMD.EXE
  2028.    PROMPT=$p$_PM$g
  2029.    PATH=C:\;C:\OS2\BIN;C:\OS2\PBIN;C:\OS2TOOLS
  2030.    INCLUDE=c:\os2tools\include
  2031.    LIB=c:\os2tools\lib
  2032.    TMP=c:\temp
  2033.    TEMP=c:\temp
  2034.    INIT=c:\init
  2035.    USER=c:\init
  2036.    ──────────────────────────────────────────────────────────────────────────
  2037.  
  2038.    Figure 3-8.  Typical environment block for protected mode session
  2039.    (displayed with the command SET).
  2040.  
  2041.    When multiple sessions are active, the command processor in each session
  2042.    maintains a "master environment" for that session.
  2043.  
  2044.    Environment variables provide information to command processors and
  2045.    application programs; they do not affect the operation of the OS/2 kernel.
  2046.    For example, the Microsoft C Compiler looks in the environment for
  2047.    INCLUDE, LIB, and TMP variables to tell it where to find header (.H) files
  2048.    and object module libraries and where to put temporary working files.
  2049.  
  2050.    The last null-terminated string in the environment block is followed by an
  2051.    additional null byte, which signifies the end of the block. Immediately
  2052.    following that byte, OS/2 places the fully qualified name of the file from
  2053.    which the program was loaded, including the logical drive identifier and
  2054.    full path from the root of that drive. This ASCIIZ string corresponds to
  2055.    argv[0] in C programs; the program can use this information to create
  2056.    another instance of itself or to find its "home directory" in order to
  2057.    load overlays or configuration files.
  2058.  
  2059.    The program filename is followed by the command line information, in the
  2060.    form of a set of ASCIIZ strings called the "argument strings." When a
  2061.    program is loaded by CMD.EXE, the first argument string is the simple name
  2062.    of the program and the second is the command tail (the command line
  2063.    excluding the program name). The argument strings are terminated by an
  2064.    additional null byte, in the same fashion as the environment block.
  2065.    Redirection or piping parameters do not appear in the argument strings
  2066.    passed by CMD.EXE because redirection is transparent to applications.
  2067.  
  2068.    Figure 3-9 contains a hex and ASCII dump of a typical program's
  2069.    environment block, filename, and argument strings. To produce the dump in
  2070.    the figure, the OS/2 FIND utility was run with the following command line:
  2071.  
  2072.    C>find /n "extrn" start.asm <Enter>
  2073.  
  2074.    ──────────────────────────────────────────────────────────────────────────
  2075.                0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDE
  2076.    005F:0000  33 58 42 4F 58 3D 63 3A 5C 6F 73 32 73 79 73 5C  3XBOX=c:\os2sys
  2077.    005F:0010  63 6F 6D 6D 61 6E 64 2E 63 6F 6D 00 43 4F 4D 53  command.com.COM
  2078.    005F:0020  50 45 43 3D 43 3A 5C 4F 53 32 53 59 53 5C 43 4D  PEC=C:\OS2SYS\C
  2079.    005F:0030  44 2E 45 58 45 00 50 41 54 48 3D 43 3A 5C 3B 43  D.EXE.PATH=C:\;
  2080.    005F:0040  3A 5C 4F 53 32 53 59 53 3B 43 3A 5C 54 4F 4F 4C  :\OS2SYS;C:\TOO
  2081.    005F:0050  53 00 44 50 41 54 48 3D 43 3A 5C 3B 43 3A 5C 4F  S.DPATH=C:\;C:\
  2082.    005F:0060  53 32 53 59 53 3B 43 3A 5C 54 4F 4F 4C 53 00 49  S2SYS;C:\TOOLS.
  2083.    005F:0070  4E 43 4C 55 44 45 3D 63 3A 5C 69 6E 63 6C 75 64  NCLUDE=c:\inclu
  2084.    005F:0080  65 00 4C 49 42 3D 63 3A 5C 6C 69 62 00 49 4E 49  e.LIB=c:\lib.IN
  2085.    005F:0090  54 3D 63 3A 5C 69 6E 69 74 00 54 4D 50 3D 63 3A  T=c:\init.TMP=c
  2086.    005F:00A0  5C 74 65 6D 70 00 54 45 4D 50 3D 63 3A 5C 74 65  \temp.TEMP=c:\t
  2087.    005F:00B0  6D 70 00 50 52 4F 4D 50 54 3D 24 70 24 5F 50 4D  mp.PROMPT=$p$_P
  2088.    005F:00C0  24 67 00 00 43 3A 5C 50 4D 46 5C 46 49 4E 44 2E  $g..C:\PMF\FIND
  2089.    005F:00D0  45 58 45 00 66 69 6E 64 00 20 2F 6E 20 22 65 78  EXE.find. /n "e
  2090.    005F:00E0  74 72 6E 22 20 73 74 61 72 74 2E 61 73 6D 00 00  trn" start.asm.
  2091.    ──────────────────────────────────────────────────────────────────────────
  2092.  
  2093.    Figure 3-9.  Sample hex dump of the information that is passed to an OS/2
  2094.    program at entry. The two zero bytes at offset 00C2H mark the end of the
  2095.    environment and are followed by the program filename. The two argument
  2096.    strings appear at offsets 00D4H and 00D9H.
  2097.  
  2098.  
  2099.  Process Execution and the API
  2100.  
  2101.    While a process is executing, it communicates with the operating system
  2102.    via the Application Program Interface (API). The API functions allow a
  2103.    program to request and release additional system resources, start and
  2104.    communicate with other processes, and perform input and output in a
  2105.    hardware-independent fashion. Some API services also allow a process to
  2106.    obtain additional information about its environment and about the system
  2107.    state and hardware capabilities (Figure 3-10 on the following page). A
  2108.    subset of the API functions, called the Family API (FAPI), corresponds
  2109.    directly to the functions supported by MS-DOS.
  2110.  
  2111.    Each API function has a named entry point, is declared with an extrn far
  2112.    statement, and is entered by a far call. Parameters for API functions are
  2113.    pushed onto the stack prior to the call. A parameter can be a value, or it
  2114.    can be an address of a value or variable (commonly a structure). For
  2115.    kernel API functions, a status code is returned to the program in register
  2116.    AX. This code is zero if the function succeeded or an error code if the
  2117.    function failed. Other returned values are placed in variables whose
  2118.    addresses were passed on the stack in the original call. (Presentation
  2119.    Manager API functions use other conventions.)
  2120.  
  2121. ╓┌─┌─────────────────────┌───────────────────────────────────────────────────╖
  2122.    Function              Description
  2123.    Function              Description
  2124.    ──────────────────────────────────────────────────────────────────────────
  2125.    DosDevConfig         Gets information about system configuration: number
  2126.                          of floppy disks, type of monitor, presence/absence
  2127.                          of 80287 or 80387, PC model type, number of printers
  2128.                          and serial ports.
  2129.  
  2130.    DosGetCtryInfo       Gets internationalization information such as the
  2131.                          country code, date and time format, and currency
  2132.                          symbol.
  2133.  
  2134.    DosGetDateTime       Gets the current date, time, and day of the week.
  2135.  
  2136.    DosGetEnv            Gets a pointer to process's environment block,
  2137.                          program filename, and command line.
  2138.  
  2139.    DosGetInfoSeg        Gets pointers to read-only global and local
  2140.                          information segments containing the process ID,
  2141.                          screen group number, current date, time, length of
  2142.                          timeslice, and other useful information.
  2143.  
  2144.    Function              Description
  2145.    ──────────────────────────────────────────────────────────────────────────
  2146. 
  2147.    DosGetMachineMode    Returns a flag indicating whether the process is
  2148.                          running in real mode or protected mode.
  2149.  
  2150.    DosGetPID            Returns the current process ID, thread ID, and
  2151.                          parent's process ID.
  2152.  
  2153.    DosGetPrty           Gets the execution priority of a thread.
  2154.  
  2155.    DosGetVersion        Gets the OS/2 version number.
  2156.  
  2157.    DosMemAvail          Returns the size of the largest free block in
  2158.                          physical memory.
  2159.  
  2160.    DosQCurDir           Gets the pathname to the current directory.
  2161.  
  2162.    DosQCurDisk          Gets the identifier for the current disk drive.
  2163.  
  2164.    DosQFSInfo           Returns information about the file system on the
  2165.    Function              Description
  2166.    ──────────────────────────────────────────────────────────────────────────
  2167.   DosQFSInfo           Returns information about the file system on the
  2168.                          specified device, such as volume label, bytes per
  2169.                          sector, total number of allocation units, and number
  2170.                          of free allocation units.
  2171.  
  2172.    DosQSysInfo          Returns system information that does not change
  2173.                          after the system is booted.
  2174.  
  2175.    DosQVerify           Gets the current setting of the system verify (read
  2176.                          after write) flag for disk operations.
  2177.  
  2178.    DosScanEnv           Searches the environment block for an environment
  2179.                          variable.
  2180.  
  2181.    VioGetConfig         Returns information about the type of video display
  2182.                          and adapter, and the amount of memory installed on
  2183.                          the adapter.
  2184.  
  2185.    VioGetMode           Returns information about the current video display
  2186.    Function              Description
  2187.    ──────────────────────────────────────────────────────────────────────────
  2188.   VioGetMode           Returns information about the current video display
  2189.                          mode, including the number of lines, columns, and
  2190.                          displayable colors.
  2191.  
  2192.    VioGetState          Returns information about the video controller
  2193.                          state: palette, intensity/blink toggle, and border
  2194.                          color.
  2195.    ──────────────────────────────────────────────────────────────────────────
  2196.  
  2197.  
  2198.    Figure 3-10.  API functions which can be used by a program to obtain
  2199.    information about its execution environment and the system state and
  2200.    capabilities. These functions are described again in more detail at
  2201.    appropriate locations elsewhere in this book.
  2202.  
  2203.    For example, the kernel function DosWrite takes as its parameters the
  2204.    handle for a file or device, the address and length of the data to be
  2205.    written, and the address of a variable in which it will return the actual
  2206.    number of bytes written.
  2207.  
  2208.    ──────────────────────────────────────────────────────────────────────────
  2209.            extrn   DosWrite:far
  2210.    stdout  equ     1               ; standard output handle
  2211.    wlen    dw      ?               ; receives length written
  2212.    msg     db      'Hello World!'  ; message to be written
  2213.    msg_len equ     $-msg           ; length of message
  2214.            .
  2215.            .
  2216.            .
  2217.            push    stdout          ; standard output handle
  2218.            push    ds              ; address of message
  2219.            push    offset DGROUP:msg
  2220.            push    msg_len         ; length of message
  2221.            push    ds              ; receives bytes written
  2222.            push    offset DGROUP:wlen
  2223.            call    DosWrite        ; transfer to OS/2
  2224.            or      ax,ax           ; was write successful?
  2225.            jnz     error           ; jump if function failed
  2226.            .
  2227.            .
  2228.            .
  2229.    ──────────────────────────────────────────────────────────────────────────
  2230.  
  2231.    Calling the OS/2 API is equally efficient from a high level language or
  2232.    from assembly language, because high level languages typically use the
  2233.    stack for parameter passing, just as the OS/2 API does. For example, to
  2234.    call an OS/2 API function from a C program, you simply declare it as far
  2235.    pascal── indicating that the parameters are pushed left to right and that
  2236.    the called routine clears the stack──and then invoke it directly by name:
  2237.  
  2238.    ──────────────────────────────────────────────────────────────────────────
  2239.    unsigned extern far pascal
  2240.      DosWrite(unsigned, char far *, unsigned, unsigned far *);
  2241.            .
  2242.            .
  2243.            .
  2244.            unsigned status, wlen;
  2245.            char *msg = "Hello World!";
  2246.            status = DosWrite(1, msg, strlen(msg), &wlen);
  2247.            .
  2248.            .
  2249.            .
  2250.    ──────────────────────────────────────────────────────────────────────────
  2251.  
  2252.    The C Compiler generates the correct code for the OS/2 call automatically.
  2253.    It introduces no execution time or space penalty, and it requires no
  2254.    intermediate library functions to shift parameters around or pop them into
  2255.    registers before transferring to the operating system. The OS/2 API
  2256.    services can be called directly in a similar fashion from Pascal, FORTRAN,
  2257.    or even Forth programs.
  2258.  
  2259.  
  2260.  Process Termination
  2261.  
  2262.    When an application program has finished processing, it terminates itself
  2263.    and passes an "exit" code back to its parent with the API function
  2264.    DosExit. A process can also be terminated by an external event, such as a
  2265.    call to DosKillProcess by its parent process, a general protection (GP)
  2266.    fault, the user's entry of Ctrl-C or Ctrl-Break, or the user's response to
  2267.    an operating system pop-up that reports a critical error.
  2268.  
  2269.    Upon termination of a process, regardless of the cause, OS/2 discontinues
  2270.    any pending I/O, closes any open files, and releases all memory and other
  2271.    system resources owned by the process. OS/2 then notifies the process's
  2272.    parent of the exit code and manner of termination, if the parent requested
  2273.    that information.
  2274.  
  2275.    A process can intercept or disable some external events to prevent its
  2276.    termination or to allow an orderly cleanup before termination. For these
  2277.    purposes, the API includes the functions DosError, DosSetSigHandler,
  2278.    DosSetVec, and DosExitList. A process cannot, however, prevent termination
  2279.    resulting from a GP fault.
  2280.  
  2281.  
  2282.  A Sample OS/2 Application
  2283.  
  2284.    Let's look at a traditional, trivial program, written in Microsoft Macro
  2285.    Assembler, that displays the message Hello World! on the standard output
  2286.    device (which defaults to the video display). This sample program is
  2287.    created from two files: HELLO.ASM (the assembly language source file) and
  2288.    HELLO.DEF (the module definition file).
  2289.  
  2290.  The Source File HELLO.ASM
  2291.  
  2292.    The file HELLO.ASM contains the assembly language source code for our
  2293.    sample program (Figure 3-11). It demonstrates the fundamental structure
  2294.    of a small model assembly language program that is destined to become an
  2295.    OS/2 EXE file. Because this program is so short and simple, a relatively
  2296.    high proportion of the source code consists of assembler directives that
  2297.    do not result in any executable code.
  2298.  
  2299.    The title directive, on line 1, specifies the text string (limited to 60
  2300.    characters) to be printed on the upper left corner of each page. The title
  2301.    directive is optional, but it cannot be used more than once in each
  2302.    assembly language source file.
  2303.  
  2304.    ──────────────────────────────────────────────────────────────────────────
  2305.     1:         title   HELLO -- Display Message on stdout
  2306.     2:         page    55,132
  2307.     3:         .286
  2308.     4:
  2309.     5: ;
  2310.     6: ; HELLO.EXE
  2311.     7: ;
  2312.     8: ; A simple OS/2 assembly language program.
  2313.     9: ;
  2314.    10: ; Copyright (C) 1986 Ray Duncan
  2315.    11: ;
  2316.    12:
  2317.    13: stdin   equ     0          ; standard input handle
  2318.    14: stdout  equ     1          ; standard output handle
  2319.    15: stderr  equ     2          ; standard error handle
  2320.    16:
  2321.    17:         extrn   DosWrite:far
  2322.    18:         extrn   DosExit:far
  2323.    19:
  2324.    20: DGROUP  group   _DATA
  2325.    21:
  2326.    22:
  2327.    23: _DATA   segment word public 'DATA'
  2328.    24:
  2329.    25: msg     db      0dh,0ah,"Hello World!",0dh,0ah
  2330.    26: msg_len equ     $-msg
  2331.    27:
  2332.    28: wlen    dw      ?          ; receives bytes written
  2333.    29:
  2334.    30: _DATA   ends
  2335.    31:
  2336.    32:
  2337.    33: _TEXT   segment word public 'CODE'
  2338.    34:
  2339.    35:         assume  cs:_TEXT,ds:DGROUP
  2340.    36:
  2341.    37: print   proc    far
  2342.    38:
  2343.    39:         push    stdout     ; standard output handle
  2344.    40:         push    ds         ; address of data
  2345.    41:         push    offset DGROUP:msg
  2346.    42:         push    msg_len    ; length of data
  2347.    43:         push    ds         ; receives bytes written
  2348.    44:         push    offset DGROUP:wlen
  2349.    45:         call    DosWrite   ; transfer to OS/2
  2350.    46:         or      ax,ax      ; was write successful?
  2351.    47:         jnz     error      ; jump if function failed
  2352.    48:
  2353.    49:         push    1          ; terminate all threads
  2354.    50:         push    0          ; return success code
  2355.    51:         call    DosExit    ; transfer to OS/2
  2356.    52:
  2357.    53: error:  push    1          ; terminate all threads
  2358.    54:         push    1          ; return error code
  2359.    55:         call    DosExit    ; transfer to OS/2
  2360.    56:
  2361.    57: print   endp
  2362.    58:
  2363.    59: _TEXT   ends
  2364.    60:
  2365.    61:         end     print
  2366.    ──────────────────────────────────────────────────────────────────────────
  2367.  
  2368.    Figure 3-11.  Source file HELLO.ASM for the sample application HELLO.EXE.
  2369.    Line numbers at left are for reference in the text and are not part of the
  2370.    actual source file.
  2371.  
  2372.    The page directive, when used with two operands as in line 2, defines the
  2373.    length and width of the printer page. These default to 66 lines long and
  2374.    80 characters wide. If you use a page directive within your listing
  2375.    without any operands, it causes a form feed to be sent to the printer and
  2376.    a heading printed. In larger programs, use the page directive liberally to
  2377.    place each of your procedures on a separate sheet for easy reading.
  2378.  
  2379.    The .286 directive (line 3) enables the assembly of 80286 nonprivileged
  2380.    instructions that are not present in the 8086 instruction set. The most
  2381.    handy of these is the push immediate instruction, which saves time and
  2382.    space when setting up parameters for an OS/2 API call.
  2383.  
  2384.    Lines 13─15 equate symbols to the predefined handles for the standard
  2385.    input, standard output, and standard error devices. Although we wouldn't
  2386.    expect these values ever to change, assigning them meaningful names makes
  2387.    the program more readable.
  2388.  
  2389.    References to OS/2 API entry points are accomplished with extrn
  2390.    directives, which assign a far attribute to the external name (lines 17
  2391.    and 18). The assembler, of course, does not know the nature of the
  2392.    procedure represented by the external name, only that it has to generate a
  2393.    far call to reach it and that the final address will be fixed up later.
  2394.  
  2395.    The declaration of DGROUP with the group directive (line 20) is mandatory.
  2396.    DGROUP is a "magic" name that specifies the application's automatic data
  2397.    segment, which also contains the default stack. Unlike MS-DOS, OS/2
  2398.    automatically initializes the DS register to point to DGROUP before it
  2399.    transfers control to the program's entry point. (The other conditions at
  2400.    entry to a protected mode application were discussed earlier in this
  2401.    chapter and are summarized in Figure 3-7 on page 37.)
  2402.  
  2403.    Now let's examine lines 23 through 30, where we declare a data segment
  2404.    that contains the variables and constants used by our program. The data
  2405.    segment is initiated with the segment directive in line 23; it is given
  2406.    the name _DATA by the label field of the statement and acquires certain
  2407.    attributes (word, public, and the classname DATA) from the operand field.
  2408.    The data segment is terminated with an ends directive in line 30. The
  2409.    Linker knows, by the class name and the association of segment name _DATA
  2410.    with the group name DGROUP, that this segment of the program does not
  2411.    contain executable machine code.
  2412.  
  2413.    Next we come to the declaration of a machine code segment that begins in
  2414.    line 33 with a segment directive and terminates line 59 with the ends
  2415.    directive. The segment instruction assigns the name _TEXT and the
  2416.    attributes word, public, and the classname CODE to the segment. You will
  2417.    find it quite helpful to read the Microsoft Macro Assembler Programmer's
  2418.    Guide for detailed explanations of each possible attribute of a segment.
  2419.    The _TEXT and _DATA segment names are simply conventions which are used by
  2420.    the Microsoft high level language compilers; these conventions are
  2421.    explained in great detail in the MASM and Microsoft C Compiler manuals.
  2422.  
  2423.    Following the segment instruction, we encounter an assume directive in
  2424.    line 35. In this statement we assert that the CS and DS segment registers
  2425.    have been loaded, at this point of execution of the final program, with
  2426.    the selectors for the _TEXT segment and DGROUP, respectively. The assume
  2427.    directive often baffles new assembly language programmers. It doesn't
  2428.    appear to do anything──it generates no code──it only notifies the
  2429.    assembler which segment registers point to which memory segments, so that
  2430.    the assembler can generate segment override prefix bytes when they are
  2431.    needed. The responsibility for actually loading the segment registers
  2432.    remains with the programmer and the operating system.
  2433.  
  2434.    Within the _TEXT segment, we come to another type of block declaration,
  2435.    begun with the proc directive in line 37 and closed with endp in line 57.
  2436.    These two instructions declare the beginning and end of a procedure, or
  2437.    block of executable code that performs a single distinct function. The
  2438.    procedure is given a name by the label in the leftmost field of the proc
  2439.    statement, and an attribute in the operand field──in this case, the name
  2440.    of the procedure is print. If the procedure carries the near attribute, it
  2441.    can be called only by other code in the same segment, whereas procedures
  2442.    with the far attribute can be called from program code located anywhere in
  2443.    the process's address space.
  2444.  
  2445.    Calls to two different OS/2 services, DosWrite and DosExit, are
  2446.    demonstrated in lines 39 through 55.
  2447.  
  2448.    DosWrite, which we encountered earlier in this chapter, performs a
  2449.    synchronous write to a file or device ("synchronous" in that the write
  2450.    request is logically completed before control returns to the requesting
  2451.    process); it is analogous to MS-DOS's Int 21H Function 40H. The returned
  2452.    status of the operation is tested in lines 46─47; the status is zero if
  2453.    the operation succeeded.
  2454.  
  2455.    DosExit terminates a process, passing a return code back to its parent
  2456.    process; it is comparable to MS-DOS's Int 21H Function 4CH. Our program
  2457.    contains two calls to DosExit: lines 49─51, which pass a return code of
  2458.    zero (signifying that our program ran successfully), and lines 53─55,
  2459.    which pass a nonzero return code (indicating that an error condition
  2460.    caused the program to terminate). The latter call executes if DosWrite
  2461.    returns an error status.
  2462.  
  2463.    The end directive in line 61 winds up our little sample program; it tells
  2464.    the Macro Assembler that it has reached the end of the source file and
  2465.    designates the initial entry point when the executable program is loaded
  2466.    by OS/2.
  2467.  
  2468.    Note that we did not declare a segment with attribute STACK in this source
  2469.    file, as we would have done under MS-DOS. In OS/2 programs, the stack is
  2470.    conveniently declared in the module definition file instead. You can,
  2471.    however, still declare it the old-fashioned way within the ASM file with a
  2472.    segment...ends sequence or with the special MASM directive .STACK.
  2473.  
  2474.  The Module Definition File HELLO.DEF
  2475.  
  2476.    The file HELLO.DEF (Figure 3-12) is the module definition file for our
  2477.    program. It demonstrates only a few of the possible directives and options
  2478.    that can be used in such a file.
  2479.  
  2480.    The NAME directive states that we are building an executable program
  2481.    rather than a dynamic link library (whose DEF file would contain LIBRARY
  2482.    instead). PROTMODE signifies that the program will run in protected mode.
  2483.  
  2484.    ──────────────────────────────────────────────────────────────────────────
  2485.    NAME HELLO WINDOWCOMPAT ; module name
  2486.    PROTMODE                ; protected mode only
  2487.    DATA PRELOAD READWRITE
  2488.    CODE PRELOAD
  2489.    STACKSIZE 4096
  2490.    ──────────────────────────────────────────────────────────────────────────
  2491.  
  2492.    Figure 3-12.  The module definition file HELLO.DEF for the sample
  2493.    application program HELLO.EXE. Note that the stack size is declared here
  2494.    rather than in the HELLO.ASM file.
  2495.  
  2496.    The lines beginning with DATA and CODE declare a few of the many possible
  2497.    segment attributes for the segments that use the Microsoft naming
  2498.    conventions _DATA and _TEXT. Segments in the program that used other names
  2499.    would be assigned attributes with the SEGMENTS directive.
  2500.  
  2501.    The stack size for the program's initial point of execution is defined by
  2502.    the STACKSIZE directive; PUSH and POP instructions will access this area
  2503.    of scratch memory. This stack is placed in DGROUP above the program's data
  2504.    and is referenced using the same selector (that is, DS=SS). Microsoft
  2505.    recommends that at least 2 KB of stack space be available at the time of
  2506.    any kernel API call, in addition to the stack space used by the program.
  2507.    Stacks for additional threads must be allocated by the program at run time
  2508.    (as is explained further in Chapter 12).
  2509.  
  2510.  
  2511.  
  2512.  ────────────────────────────────────────────────────────────────────────────
  2513.  Chapter 4  OS/2 Programming Tools
  2514.  
  2515.    Writing an OS/2 Kernel application is an iterative cycle with four basic
  2516.    steps:
  2517.  
  2518.    1.  Use a text editor to create or modify an ASCII source code file and
  2519.        module definition (DEF) file.
  2520.  
  2521.    2.  Use an assembler or high level language compiler to translate the
  2522.        source file into relocatable object code.
  2523.  
  2524.    3.  Use a linker to transform the relocatable object code into an
  2525.        executable OS/2 load module.
  2526.  
  2527.    4.  Use a debugger to methodically test and debug the program.
  2528.  
  2529.    An OS/2 software developer might find the following additional utilities
  2530.    necessary or helpful:
  2531.  
  2532.    ■  LIB, which creates and maintains object module libraries
  2533.  
  2534.    ■  IMPLIB, which creates import reference libraries
  2535.  
  2536.    ■  CREF, which generates a cross-reference listing
  2537.  
  2538.    ■  MAKE, which compares dates of files and carries out operations based on
  2539.       the result of the comparison
  2540.  
  2541.    ■  BIND, which converts a protected mode executable file into one that can
  2542.       be used in either protected mode or real mode
  2543.  
  2544.    ■  MARKEXE, which marks an application's header so that it can run in a
  2545.       window under Presentation Manager
  2546.  
  2547.    ■  RC, which compiles Presentation Manager resources such as icons,
  2548.       bitmaps, and menus and merges them into an application
  2549.  
  2550.    ■  CodeView, Microsoft's symbolic debugger
  2551.  
  2552.    This chapter is an operational overview of the Microsoft (and IBM) tools
  2553.    for programming OS/2 Kernel applications, including the Macro Assembler, C
  2554.    Compiler, Linker, Library Manager, MAKE, CREF, and BIND. The CodeView
  2555.    debugger, the MARKEXE utility, and the tools specific to the Presentation
  2556.    Manager (such as RC) are not discussed here, and IMPLIB is covered in
  2557.    Chapter 19. Even if your preferred programming language is not C or
  2558.    assembler, you will need at least a passing familiarity with the tools
  2559.    described in this chapter because all the examples in the IBM and
  2560.    Microsoft OS/2 reference manuals are written in either C or assembler.
  2561.  
  2562.    The survey in this chapter, together with the sample programs and
  2563.    reference section elsewhere in this book, is intended to provide the
  2564.    experienced programmer with sufficient information to begin writing useful
  2565.    programs. If you lack a background in C, assembly language, or the
  2566.    80286/386 architecture, refer to the tutorial and reference works listed
  2567.    in Appendix C.
  2568.  
  2569.  
  2570.  File Types
  2571.  
  2572.    The OS/2 programming tools can create and process files of many types. The
  2573.    extensions conventionally used for these files appear in Figure 4-1.
  2574.  
  2575. ╓┌─┌─────────────┌───────────────────────────────────────────────────────────╖
  2576.    Extension     File Type
  2577.    ──────────────────────────────────────────────────────────────────────────
  2578.    ASM           Assembly language source file
  2579.  
  2580.    C             C program source file
  2581.  
  2582.    CRF           Cross-reference information file, produced by the assembler
  2583.                  for processing by CREF
  2584.  
  2585.    Extension     File Type
  2586.    ──────────────────────────────────────────────────────────────────────────
  2587. 
  2588.    DEF           Module definition file, which specifies the characteristics
  2589.                  of each program segment, the size of the heap and stack, and
  2590.                  any imported and exported functions for dynamic linking
  2591.  
  2592.    DLL           An OS/2 dynamic link library
  2593.  
  2594.    EXE           An OS/2 segmented executable load module; requires
  2595.                  additional relocation and dynamic linking at run time
  2596.  
  2597.    H             C "header" file; contains C source code for constants,
  2598.                  macros, and functions and is merged into another C program
  2599.                  with #include
  2600.  
  2601.    INC           "Include" file for assembly language programs; typically
  2602.                  contains macros and/or equates for systemwide values such as
  2603.                  error codes
  2604.  
  2605.    LIB           Object module library file comprising one or more OBJ files,
  2606.    Extension     File Type
  2607.    ──────────────────────────────────────────────────────────────────────────
  2608.   LIB           Object module library file comprising one or more OBJ files,
  2609.                  indexed and manipulated by the LIB utility
  2610.  
  2611.    LST           Program listing produced by the assembler; includes machine
  2612.                  code, memory locations, original program text, and any error
  2613.                  messages
  2614.  
  2615.    MAP           Listing of symbols and their locations within a load module,
  2616.                  produced by the Linker
  2617.  
  2618.    OBJ           Relocatable object code file produced by an assembler or
  2619.                  compiler
  2620.  
  2621.    RC            Resource Compiler source file; specifies menus, dialog
  2622.                  boxes, strings, icons, cursors, and bitmaps for inclusion in
  2623.                  a PM application
  2624.  
  2625.    REF           Cross-reference listing produced by CREF from the
  2626.                  information in a CRF file
  2627.    Extension     File Type
  2628.    ──────────────────────────────────────────────────────────────────────────
  2629.                 information in a CRF file
  2630.  
  2631.    RES           Intermediate file produced by Resource Compiler from an RC
  2632.                  file; merged into an EXE file to build a PM application
  2633.  
  2634.    SYS           An OS/2 device driver
  2635.    ──────────────────────────────────────────────────────────────────────────
  2636.  
  2637.  
  2638.    Figure 4-1.  Conventionally assigned extensions associated with various
  2639.    file types under OS/2.
  2640.  
  2641.  
  2642.  Using the Macro Assembler
  2643.  
  2644.    The Microsoft Macro Assembler is distributed as the file MASM.EXE. When it
  2645.    begins a program translation, the Macro Assembler needs the following
  2646.    information: the name of the file containing the source program, the
  2647.    filename for the object program to be created, the destination of the
  2648.    program listing, and the filename for the information that is later
  2649.    processed by the cross-reference utility (CREF).
  2650.  
  2651.    You can invoke the assembler in two ways. If you enter the name of the
  2652.    assembler alone, you will be prompted for the names of each of the various
  2653.    input and output files. The assembler supplies reasonable defaults for all
  2654.    responses except the source filename. For example:
  2655.  
  2656.    [C:\] MASM  <Enter>
  2657.    Microsoft (R) Macro Assembler Version 5.10
  2658.    Copyright (C) Microsoft Corp 1981, 1988. All rights reserved.
  2659.  
  2660.    Source filename [.ASM]: HELLO  <Enter>
  2661.    Object filename [HELLO.OBJ]:  <Enter>
  2662.    Source listing  [NUL.LST]:  <Enter>
  2663.    Cross-reference [NUL.CRF]:  <Enter>
  2664.  
  2665.      48982 Bytes symbol space free
  2666.  
  2667.          0 Warning Errors
  2668.          0 Severe  Errors
  2669.  
  2670.    [C:\]
  2671.  
  2672.    You can use a logical device name (such as PRN or COM1) at any of the
  2673.    assembler prompts to send any of the outputs of the assembler to a
  2674.    character device rather than to a file. Note that the default destination
  2675.    for the listing and cross-reference files is the NUL device; that is, no
  2676.    file is created. If you end any response with a semicolon, the remaining
  2677.    responses are assumed to be the default.
  2678.  
  2679.    A more efficient way to use the assembler is to supply all parameters on
  2680.    the command line:
  2681.  
  2682.      MASM [options] source,[object],[listing],[crossref]
  2683.  
  2684.    For example, the following command lines are equivalent to the interactive
  2685.    session above:
  2686.  
  2687.    [C:\] MASM HELLO,,NUL,NUL  <Enter>
  2688.  
  2689.    or
  2690.  
  2691.    [C:\] MASM HELLO;  <Enter>
  2692.  
  2693.    These commands use the file HELLO.ASM as source, generate the object code
  2694.    file HELLO.OBJ, and send the listing and cross-reference files to the
  2695.    bit bucket.
  2696.  
  2697.    The Macro Assembler accepts a number of optional parameters or switches on
  2698.    the command line that control code generation and output files. The
  2699.    switches accepted by MASM versions 5.1 and later are listed in Figure
  2700.    4-2. In other versions of the Microsoft Macro Assembler, additional or
  2701.    fewer switches might be available. For exact instructions, see the manual
  2702.    that corresponds to the version of the assembler you are using.
  2703.  
  2704. ╓┌─┌─────────────┌───────────────────────────────────────────────────────────╖
  2705.    Switch        Meaning
  2706.    ──────────────────────────────────────────────────────────────────────────
  2707.    /A            Arranges segments in alphabetic order
  2708.  
  2709.    /Bn           Sets size of source file buffer (in KB)
  2710.  
  2711.    Switch        Meaning
  2712.    ──────────────────────────────────────────────────────────────────────────
  2713. 
  2714.    /C            Forces creation of a cross-reference (CRF) file
  2715.  
  2716.    /D            Produces listing on both passes (to find phase errors)
  2717.  
  2718.    /Dsymbol      Defines symbol as a null text string (symbol can be
  2719.                  referenced by conditional assembly directives in file)
  2720.  
  2721.    /E            Assembles for 80x87 numeric coprocessor emulator using IEEE
  2722.                  real number format
  2723.  
  2724.    /H            Displays the MASM command line syntax and available options
  2725.  
  2726.    /Ipath        Sets search path for include files
  2727.  
  2728.    /L            Forces creation of a program listing file
  2729.  
  2730.    /LA           Forces listing of all generated code
  2731.  
  2732.    Switch        Meaning
  2733.    ──────────────────────────────────────────────────────────────────────────
  2734. 
  2735.    /ML           Preserves case sensitivity in all names (uppercase names are
  2736.                  distinct from their lowercase equivalents)
  2737.  
  2738.    /MU           Converts all lowercase names to uppercase
  2739.  
  2740.    /MX           Preserves lowercase in external names only (names defined
  2741.                  with public or extrn directives)
  2742.  
  2743.    /N            Suppresses generation of tables of macros, structures,
  2744.                  records, segments, groups, and symbols at the end of the
  2745.                  listing
  2746.  
  2747.    /P            Checks for impure code in 80286/386 protected mode
  2748.  
  2749.    /S            Arranges segments in order of occurrence (default)
  2750.  
  2751.    /T            "Terse" mode, suppresses all messages unless errors are
  2752.                  encountered during the assembly
  2753.    Switch        Meaning
  2754.    ──────────────────────────────────────────────────────────────────────────
  2755.                 encountered during the assembly
  2756.  
  2757.    /V            "Verbose" mode, reports number of lines and symbols at end
  2758.                  of assembly
  2759.  
  2760.    /Wn           Sets error display ("warning") level, n = 0, 1, or 2
  2761.  
  2762.    /X            Forces listing of false conditionals
  2763.  
  2764.    /Z            Displays source lines containing errors on the screen
  2765.  
  2766.    /Zd           Includes line number information in OBJ file
  2767.  
  2768.    /Zi           Includes line number and symbol information in OBJ file
  2769.    ──────────────────────────────────────────────────────────────────────────
  2770.  
  2771.  
  2772.    Figure 4-2.  Summary of Microsoft Macro Assembler version 5.1 switches and
  2773.    options.
  2774.  
  2775.    The assembler allows you to override the default extensions on any file──a
  2776.    "feature" that can be rather dangerous. For example, if in the previous
  2777.    example you had responded to the Object filename prompt with HELLO.ASM,
  2778.    the assembler would have accepted the entry without comment and
  2779.    overwritten your source file. You are unlikely to make this mistake in the
  2780.    interactive command mode, but you must be very careful with file
  2781.    extensions when MASM is run from a batch or "make'' file.
  2782.  
  2783.  
  2784.  Using the C Compiler
  2785.  
  2786.    The Microsoft C Compiler consists of three executable files (C1.EXE,
  2787.    C2.EXE, and C3.EXE) that implement the C preprocessor, language
  2788.    translator, code generator, and code optimizer. An additional control
  2789.    program, CL.EXE, is provided to execute the three compiler files in order
  2790.    (passing each the necessary information about filenames and compilation
  2791.    options) and to execute the Microsoft Segmented Executable Linker
  2792.    (LINK.EXE).
  2793.  
  2794.    Before you use the C Compiler and the Linker, set up four environment
  2795.    variables:
  2796.  
  2797.    ──────────────────────────────────────────────────────────────────────────
  2798.    PATH=path      Used by CL.EXE to find the three executable C compiler
  2799.                   files (C1, C2, and C3) if they are not found in the current
  2800.                   directory
  2801.    INCLUDE=path   Specifies the location of include files that do not reside
  2802.                   in the current directory
  2803.    LIB=path       Specifies the location(s) for object code libraries that do
  2804.                   not reside in the current directory
  2805.    TMP=path       Specifies the location for temporary working files created
  2806.                   by the C Compiler and Linker
  2807.    ──────────────────────────────────────────────────────────────────────────
  2808.  
  2809.    CL.EXE does not support an interactive mode or response files. You always
  2810.    invoke it with a command line of the form:
  2811.  
  2812.      CL [options] file [file ...]
  2813.  
  2814.    You can list any number of files; if a file has a C extension, it is
  2815.    compiled into a relocatable object (OBJ) module file. Ordinarily, if no
  2816.    errors are encountered during compilation, all resulting OBJ files and any
  2817.    additional OBJ files specified on the command line are automatically
  2818.    passed to the Linker along with the names of the appropriate runtime
  2819.    libraries.
  2820.  
  2821.    The C Compiler has many options for controlling its memory models, output
  2822.    files, and code generation and optimization. These options are summarized
  2823.    in Figure 4-3. Unlike the options available in MASM, the options
  2824.    available in the C Compiler are case-sensitive. The arcane switch syntax
  2825.    is largely derived from UNIX, so don't expect it to make any sense.
  2826.  
  2827. ╓┌─┌────────────────────┌────────────────────────────────────────────────────╖
  2828.    Switch               Meaning
  2829.    ──────────────────────────────────────────────────────────────────────────
  2830.    /Ax                  Selects memory model
  2831.                         d = assume DS = SS
  2832.                         C = compact model
  2833.                         H = huge model
  2834.                         L = large model
  2835.                         M = medium model
  2836.                         S = small model (default)
  2837.    Switch               Meaning
  2838.    ──────────────────────────────────────────────────────────────────────────
  2839.                        S = small model (default)
  2840.                         u = assume DS != SS; DS reloaded on function entry
  2841.                         w = assume DS != SS; DS not reloaded on function
  2842.                         entry
  2843.    /c                   Compiles only, does not invoke Linker
  2844.    /C                   Does not strip comments
  2845.    /D<name>[=text]      Defines macro or constant used in source file
  2846.    /E                   Sends preprocessor output to standard output
  2847.    /EP                  Same as /E, but no line numbers
  2848.    /F <n>               Sets stack size (in hex bytes)
  2849.    /Fa[filename]        Generates assembly listing
  2850.    /Fb[filename]        Invokes BIND and LINK to create dual mode executable
  2851.    /Fc[filename]        Generates mixed source/object listing
  2852.    /Fe[filename]        Forces executable filename
  2853.    /Fl[filename]        Generates object listing file
  2854.    /Fm[filename]        Generates map file
  2855.    /Fo[filename]        Forces object module filename
  2856.    /FPx                 Floating point control
  2857.                         a = calls with alternate math library
  2858.    Switch               Meaning
  2859.    ──────────────────────────────────────────────────────────────────────────
  2860.                        a = calls with alternate math library
  2861.                         c = calls with emulator library
  2862.                         c87 = calls with 8087 library
  2863.                         i = inline with emulator (default)
  2864.                         i87 = inline with 8087
  2865.    /Fs[filename]        Generates source listing
  2866.    /Gx                  Selects code generation
  2867.                         0 = 8086 instructions (default)
  2868.                         1 = 186 instructions
  2869.                         2 = 286 instructions
  2870.                         c = Pascal style function calls
  2871.                         s = no stack checking
  2872.                         t[n] = data size threshold
  2873.    /H<n>                Specifies external name length
  2874.    /HELP                Displays the C Compiler command line syntax and
  2875.                         available options
  2876.    /I<path>             Specifies additional #include path
  2877.    /J                   Sets default char type to unsigned
  2878.    /Lc                  Links for execution under MS-DOS
  2879.    Switch               Meaning
  2880.    ──────────────────────────────────────────────────────────────────────────
  2881.   /Lc                  Links for execution under MS-DOS
  2882.    /link [options]      Passes switches and library names to Linker
  2883.    /Lp                  Links for execution under OS/2
  2884.    /Lr                  Same as /Lc
  2885.    /ND name             Assigns name to data segment
  2886.    /NM name             Assigns name to module
  2887.    /NT name             Assigns name to code (text) segment
  2888.    /Ox                  Selects optimization
  2889.                         a = ignore aliasing
  2890.                         d = disable optimizations
  2891.                         i = enable intrinsic functions
  2892.                         l = enable loop optimizations
  2893.                         n = disable "unsafe" optimizations
  2894.                         p = enable precision optimizations
  2895.                         r = disable in-line return
  2896.                         s = optimize for space
  2897.                         t = optimize for speed (default)
  2898.                         w = ignore aliasing except across function calls
  2899.                         x = maximum optimization (/Oailt /Gs)
  2900.    Switch               Meaning
  2901.    ──────────────────────────────────────────────────────────────────────────
  2902.                        x = maximum optimization (/Oailt /Gs)
  2903.    /P                   Sends preprocessor output to file
  2904.    /Sx                  Source listing control
  2905.                         l<columns> = set line width
  2906.                         p<lines> = set page length
  2907.                         s<string> = set subtitle string
  2908.                         t<string> = set title string
  2909.    /Tc<file>            Compiles file without C extension
  2910.    /u                   Removes all predefined macros
  2911.    /U<name>             Removes specified predefined macro
  2912.    /V<string>           Sets version string
  2913.    /W<n>                Sets warning level (0─3)
  2914.    /X                   Ignores the INCLUDE environment variable when
  2915.                         searching for include files
  2916.    /Zx                  Miscellaneous compilation control
  2917.                         a = disable extensions
  2918.                         c = make Pascal functions case-insensitive
  2919.                         d = line number information
  2920.                         e = enable extensions (default)
  2921.    Switch               Meaning
  2922.    ──────────────────────────────────────────────────────────────────────────
  2923.                        e = enable extensions (default)
  2924.                         g = generate declarations
  2925.                         i = symbolic debugging information
  2926.                         l = remove default library information
  2927.                         p<n> = pack structures on n-byte boundary
  2928.                         s = syntax check only
  2929.    ──────────────────────────────────────────────────────────────────────────
  2930.  
  2931.  
  2932.    Figure 4-3.  Summary of Microsoft C version 5.1 compiler switches and
  2933.    options.
  2934.  
  2935.  
  2936.  Using the Linker
  2937.  
  2938.    The object module produced by an assembler or compiler from a source file
  2939.    is in a form that contains relocation information. It can also contain
  2940.    unresolved references to external locations or routines. The Linker
  2941.    accepts one or more object modules, resolves external references, includes
  2942.    any necessary routines from designated libraries, performs any offset
  2943.    relocations that might be necessary, and writes a file that can be loaded
  2944.    and executed by the OS/2 operating system.
  2945.  
  2946.    As with the Macro Assembler, you can supply parameters to the Linker
  2947.    interactively or enter all the required information on a single command
  2948.    line. If you simply enter the utility name alone, the following dialog
  2949.    ensues.
  2950.  
  2951.    [C:\] LINK  <Enter>
  2952.  
  2953.    Microsoft (R) Segmented-Executable Linker  Version 5.01.21
  2954.    Copyright (C) Microsoft Corp 1984-1988.  All rights reserved.
  2955.  
  2956.    Object Modules [.OBJ]: HELLO  <Enter>
  2957.    Run File [HELLO.EXE]:  <Enter>
  2958.    List File [NUL.MAP]: HELLO  <Enter>
  2959.    Libraries [.LIB]: OS2  <Enter>
  2960.    Definitions File [NUL.DEF]: HELLO  <Enter>
  2961.  
  2962.    [C:\]
  2963.  
  2964.    The input files for this run of the Linker are the object module
  2965.    HELLO.OBJ, the module definition file HELLO.DEF, and the import library
  2966.    OS2.LIB (which contains dynlink reference records for the OS/2 kernel
  2967.    API). The output files are HELLO.EXE (the executable program) and
  2968.    HELLO.MAP (the load map produced by the Linker after all references and
  2969.    addresses are resolved, Figure 4-4).
  2970.  
  2971.    ──────────────────────────────────────────────────────────────────────────
  2972.    HELLO
  2973.  
  2974.    Start     Length     Name                   Class
  2975.    0001:0000 00012H     _DATA                  DATA
  2976.    0002:0000 00027H     _TEXT                  CODE
  2977.  
  2978.    Origin   Group
  2979.    0001:0   DGROUP
  2980.  
  2981.    Program entry point at 0002:0000
  2982.    ──────────────────────────────────────────────────────────────────────────
  2983.  
  2984.    Figure 4-4.  Map produced by Linker during the generation of the HELLO.EXE
  2985.    program, Figure 3-11 in Chapter 3. The program contains one CODE segment
  2986.    and one DATA segment. The STACK size was set by an entry in the HELLO.DEF
  2987.    file and is therefore not shown as a segment. The first instruction to be
  2988.    executed lies in the first byte of the CODE segment. This simple program
  2989.    declares only one group: the "automatic data group" DGROUP, which must be
  2990.    present in all OS/2 programs.
  2991.  
  2992.    You can obtain the same result more quickly by entering all parameters on
  2993.    the command line:
  2994.  
  2995.      LINK [options] objectfiles, [exefile], [mapfile], [libraries], [deffile]
  2996.  
  2997.    Thus, the command line equivalent to the session above is as follows:
  2998.  
  2999.    [C:\] LINK HELLO,,HELLO,OS2,HELLO  <Enter>
  3000.  
  3001.    The entry of a semicolon as the last character of the command line causes
  3002.    the Linker to assume the default values for all further parameters. This
  3003.    is not normally a useful feature in OS/2 development, however, because
  3004.    dynamic link reference libraries and module definition files are almost
  3005.    always required.
  3006.  
  3007.    A third method of commanding the Linker is to use a response file. A
  3008.    response file contains lines of text that correspond to the responses you
  3009.    would give the Linker interactively. The name of the response file is
  3010.    specified on the command line with a leading @ character:
  3011.  
  3012.      LINK @filename
  3013.  
  3014.    The name of a response file can also be entered at any prompt. If the
  3015.    response file is not complete, a prompt is issued for the missing
  3016.    information.
  3017.  
  3018.    When entering Linker commands, you can specify multiple object files with
  3019.    the + operator or with spaces, as in the following:
  3020.  
  3021.    [C:\] LINK HELLO+VMODE+DOSINT,MYPROG,,OS2,HELLO  <Enter>
  3022.  
  3023.    which links the files HELLO.OBJ, VMODE.OBJ, and DOSINT.OBJ, using the
  3024.    import library OS2.LIB, and produces a file named MYPROG.EXE. LINK uses
  3025.    the current drive and directory if you do not explicitly specify otherwise
  3026.    in a filename. It does not automatically use the same drive and directory
  3027.    you specified for a previous file on the same command line.
  3028.  
  3029.    If multiple library files are to be searched, enter them in the libraries
  3030.    field separated by + or space characters. You can specify a maximum of 32
  3031.    libraries. The default libraries provided with each of the high level
  3032.    language compilers are searched during the linkage process (if the Linker
  3033.    can find them), unless you explicitly exclude them with the /NOD option.
  3034.    The Linker first looks for libraries in the current directory of the
  3035.    default disk drive, then along any paths that are provided in the command
  3036.    line, and finally along the path or paths specified by the LIB variable
  3037.    (if it is present in the environment).
  3038.  
  3039.    The Linker accepts a number of optional parameters as part of the command
  3040.    line or at the end of any interactive prompt. These switches are listed in
  3041.    Figure 4-5. The number of options available varies between different
  3042.    versions of the Microsoft Linker. See your Linker reference manual for
  3043.    detailed information about your particular version.
  3044.  
  3045. ╓┌─┌─────────────┌───────────────────────────────────────────────────────────╖
  3046.    Switch        Meaning
  3047.    Switch        Meaning
  3048.    ──────────────────────────────────────────────────────────────────────────
  3049.    /A:n          Sets segment sector alignment factor. N must be a power of 2
  3050.                  (default = 512). Not related to logical segment alignment
  3051.                  (byte, word, para, page, etc.). Relevant to segmented
  3052.                  executable files (Microsoft Windows and OS/2) only.
  3053.  
  3054.    /B            Suppresses Linker prompt if a library cannot be found in the
  3055.                  current directory or in the locations specified by the LIB
  3056.                  environment variable.
  3057.  
  3058.    /CO           Includes symbolic debugging information in the EXE file for
  3059.                  use by CodeView.
  3060.  
  3061.    /CP           Sets the field in the EXE file header controlling the amount
  3062.                  of memory allocated to the program in addition to the memory
  3063.                  required for the program's code, stack, and initialized
  3064.                  data. Relevant to real mode programs only.
  3065.  
  3066.    /DO           Uses standard Microsoft segment naming and ordering
  3067.                  conventions.
  3068.    Switch        Meaning
  3069.    ──────────────────────────────────────────────────────────────────────────
  3070.                 conventions.
  3071.  
  3072.    /DS           Loads data at the high end of the data segment. Relevant to
  3073.                  real mode programs only.
  3074.  
  3075.    /E            Packs executable file by removing sequences of repeated
  3076.                  bytes and optimizing relocation table.
  3077.  
  3078.    /F            Optimizes far calls to labels that are within the same
  3079.                  physical segment for speed by replacing them with near calls
  3080.                  and NOPs.
  3081.  
  3082.    /HE           Displays information about available options.
  3083.  
  3084.    /HI           Loads the program as high in memory as possible. Relevant to
  3085.                  real mode programs only.
  3086.  
  3087.    /I            Displays information about link progress, including pass
  3088.                  numbers and the names of object files being linked.
  3089.    Switch        Meaning
  3090.    ──────────────────────────────────────────────────────────────────────────
  3091.                 numbers and the names of object files being linked.
  3092.  
  3093.    /INC          Forces production of SYM and ILK files for subsequent use by
  3094.                  ILINK (incremental Linker). Cannot be used with /EXEPACK.
  3095.  
  3096.    /LI           Writes the address of the first instruction that corresponds
  3097.                  to each source code line to the map file. Has no effect if
  3098.                  compiler does not include line number information in the
  3099.                  object module. Forces creation of a map file.
  3100.  
  3101.    /M[:n]        Forces creation of a MAP file listing all public symbols
  3102.                  sorted by name and by location. The optional value n is the
  3103.                  maximum number of symbols which may be sorted (default =
  3104.                  2048); when n is supplied, the alphabetically sorted list is
  3105.                  omitted.
  3106.  
  3107.    /NOD          Skips search of any default compiler libraries that are
  3108.                  specified in OBJ file.
  3109.  
  3110.    Switch        Meaning
  3111.    ──────────────────────────────────────────────────────────────────────────
  3112. 
  3113.    /NOE          Ignores the extended library dictionary (if it is present).
  3114.                  The extended dictionary ordinarily provides the linker with
  3115.                  information about inter-module dependencies to speed up
  3116.                  linking.
  3117.  
  3118.    /NOF          Disables optimization of far calls to labels within the same
  3119.                  segment.
  3120.  
  3121.    /NOG          Causes the Linker to ignore group associations when
  3122.                  assigning addresses to data and code items. Revelant for
  3123.                  real mode programs only.
  3124.  
  3125.    /NOI          Does not ignore case in names during LINK.
  3126.  
  3127.    /NON          Arranges segments as for /DO but does not insert 16 null
  3128.                  bytes at start of _TEXT segment.
  3129.  
  3130.    /NOP          Does not pack contiguous logical code segments into a single
  3131.    Switch        Meaning
  3132.    ──────────────────────────────────────────────────────────────────────────
  3133.   /NOP          Does not pack contiguous logical code segments into a single
  3134.                  physical segment.
  3135.  
  3136.    /O:n          Controls the interrupt number used by the overlay manager
  3137.                  supplied with some Microsoft high level languages. Relevant
  3138.                  for real mode programs only.
  3139.  
  3140.    /PAC[:n]      Packs contiguous logical code segments into a single
  3141.                  physical code segment. N is the maximum size for each packed
  3142.                  physical code segment (default = 65,536 bytes). Segments in
  3143.                  different groups are not packed.
  3144.  
  3145.    /PADC:n       Adds n filler bytes to the end of each code module, so that
  3146.                  a larger module can be inserted later with ILINK
  3147.                  (incremental Linker).
  3148.  
  3149.    /PADD:n       Adds n filler bytes to the end of each data module, so that
  3150.                  a larger module can be inserted later with ILINK
  3151.                  (incremental Linker).
  3152.    Switch        Meaning
  3153.    ──────────────────────────────────────────────────────────────────────────
  3154.                 (incremental Linker).
  3155.  
  3156.    /PAU          Pauses during linking, allowing a change of diskettes before
  3157.                  the EXE file is written.
  3158.  
  3159.    /SE:n         Sets the maximum number of segments in the linked program
  3160.                  (default = 128).
  3161.  
  3162.    /ST:n         Sets stack size of program in bytes; ignores stack segment
  3163.                  size declarations within object modules.
  3164.  
  3165.    /W            Displays warning messages for offsets that are relative to a
  3166.                  segment base that is not the same as the group base.
  3167.                  Relevant to segmented executable files (Windows and OS/2)
  3168.                  only.
  3169.    ──────────────────────────────────────────────────────────────────────────
  3170.  
  3171.  
  3172.    Figure 4-5.  Switches accepted by Linker versions 5.01 and later.
  3173.  
  3174.  
  3175.  Using the CREF Utility
  3176.  
  3177.    The CREF cross-reference utility processes a CRF file produced by the
  3178.    assembler, creating an ASCII text file with the default extension REF. The
  3179.    file contains a cross-reference listing of all symbols declared in the
  3180.    program and the line numbers in which they are referenced (see Figure
  3181.    4-6). The line numbers given in the cross-reference listing correspond to
  3182.    the line numbers that are generated by the assembler in the program
  3183.    listing (LST) file──not to any physical line count in the original source
  3184.    file.
  3185.  
  3186.    ──────────────────────────────────────────────────────────────────────────
  3187.    Microsoft Cross-Reference  Version 5.10      Thu Feb 09 10:04:22 1989
  3188.    HELLO -- Display Message on stdout
  3189.  
  3190.      Symbol Cross-Reference            (# definition, + modification)    Cref-
  3191.  
  3192.    @CPU . . . . . . . . . . . . . .    1#     3#
  3193.    @VERSION . . . . . . . . . . . .    1#
  3194.  
  3195.    CODE . . . . . . . . . . . . . .   33
  3196.  
  3197.    DATA . . . . . . . . . . . . . .   23
  3198.    DGROUP . . . . . . . . . . . . .   20     35     41     44
  3199.    DOSEXIT. . . . . . . . . . . . .   18#    51     55
  3200.    DOSWRITE . . . . . . . . . . . .   17#    45
  3201.  
  3202.    ERROR. . . . . . . . . . . . . .   47     53#
  3203.  
  3204.    MSG. . . . . . . . . . . . . . .   25#    41
  3205.    MSG_LEN. . . . . . . . . . . . .   26#    42
  3206.  
  3207.    PRINT. . . . . . . . . . . . . .   37#    57     61
  3208.  
  3209.    STDERR . . . . . . . . . . . . .   15#
  3210.    STDIN. . . . . . . . . . . . . .   13#
  3211.    STDOUT . . . . . . . . . . . . .   14#    39
  3212.  
  3213.    WLEN . . . . . . . . . . . . . .   28#    44
  3214.  
  3215.    _DATA. . . . . . . . . . . . . .   20     23#    30
  3216.    _TEXT. . . . . . . . . . . . . .   33#    35     59
  3217.  
  3218.    17 Symbols
  3219.    ──────────────────────────────────────────────────────────────────────────
  3220.  
  3221.    Figure 4-6.  Cross-reference listing HELLO.REF produced by the CREF
  3222.    utility from the file HELLO.CRF, for the HELLO.EXE program example, Figure
  3223.    3-11 in Chapter 3. The symbols declared in the program are listed on the
  3224.    left in alphabetic order. To the right of each symbol is a list of all of
  3225.    the lines where that symbol is referenced. The number with a # sign after
  3226.    it denotes the line where the symbol is declared. Numbers followed by a +
  3227.    sign indicate that the symbol is modified at the specified line.
  3228.  
  3229.    You can supply parameters for CREF interactively or include them on a
  3230.    single command line. If you enter the name CREF alone, it prompts you for
  3231.    the input and output filenames:
  3232.  
  3233.    [C:\] CREF  <Enter>
  3234.    Microsoft (R) Cross-Reference Utility  Version 5.10
  3235.    Copyright (C) Microsoft Corp 1981-1985, 1987.  All rights reserved.
  3236.  
  3237.    Cross-reference [.CRF]: HELLO  <Enter>
  3238.    Listing [HELLO.REF]:  <Enter>
  3239.  
  3240.    17 Symbols
  3241.  
  3242.    [C:\]
  3243.  
  3244.    The parameters can also be entered on the command line as follows:
  3245.  
  3246.      CREF crossreffile,[listingfile]
  3247.  
  3248.    For example, the command line equivalent to the interactive session above
  3249.    is the following:
  3250.  
  3251.    [C:\] CREF HELLO;  <Enter>
  3252.  
  3253.    If CREF cannot find the specified CRF file, an error message is displayed.
  3254.    Otherwise, the cross-reference listing is left in the specified file on
  3255.    the disk. To direct CREF to send the cross-reference listing directly to a
  3256.    character device (such as the printer) as it is generated, respond with
  3257.    the name of the device at the Listing prompt.
  3258.  
  3259.  
  3260.  Using the Library Manager
  3261.  
  3262.    Although the object modules that are produced by the assembler or by high
  3263.    level language compilers can be linked directly into executable load
  3264.    modules, they can also be collected into special files called object
  3265.    module libraries. The modules in a library are indexed by name and by the
  3266.    public symbols they contain so that the Linker can extract them to satisfy
  3267.    external references in a program.
  3268.  
  3269.    The Microsoft Library Manager (LIB) creates and maintains object module
  3270.    libraries──adding, updating, and deleting object modules as needed. The
  3271.    Librarian is also capable of checking a library file for internal
  3272.    consistency, or of printing a table of its contents. Figure 4-7 shows a
  3273.    portion of such a table for the Microsoft C library SLIBC.LIB. The first
  3274.    part of the listing is an alphabetic list of all public names declared in
  3275.    all of the modules in the library. Each name is associated with the object
  3276.    module to which it belongs. The second part of the listing is an
  3277.    alphabetic list of the module names in the library. Each name is followed
  3278.    by the offset of the object module within the library file and its actual
  3279.    size in bytes. Following the entry for each module is a summary of the
  3280.    public names declared within it.
  3281.  
  3282.    LIB follows the command conventions of most other Microsoft programming
  3283.    tools. You must supply it with the name of a library file to work on, one
  3284.    or more operations, the name of a listing file or device, and (optionally)
  3285.    the name of the output library. If no name is specified for the output
  3286.    library, it is given the same name as the input library, and the extension
  3287.    of the input library is changed to BAK.
  3288.  
  3289.    ──────────────────────────────────────────────────────────────────────────
  3290.    _abort............abort             _abs..............abs
  3291.    _access...........access            _asctime..........asctime
  3292.    _atof.............atof              _atoi.............atoi
  3293.    _atol.............atol              _bdos.............bdos
  3294.    _brk..............brk               _brkctl...........brkctl
  3295.    _bsearch..........bsearch           _calloc...........calloc
  3296.    _cgets............cgets             _chdir............dir
  3297.    _chmod............chmod             _chsize...........chsize
  3298.  
  3299.         .
  3300.         .
  3301.         .
  3302.  
  3303.    _exit             Offset: 00000010H  Code and data size: 44H
  3304.      __exit
  3305.  
  3306.    _filbuf           Offset: 00000160H  Code and data size: BBH
  3307.      __filbuf
  3308.  
  3309.    _file             Offset: 00000300H  Code and data size: CAH
  3310.      __iob             __iob2            __lastiob
  3311.  
  3312.         .
  3313.         .
  3314.         .
  3315.    ──────────────────────────────────────────────────────────────────────────
  3316.  
  3317.    Figure 4-7.  Extract from the table-of-contents listing produced by the
  3318.    Library Manager for the Microsoft C library SCLIBC.LIB.
  3319.  
  3320.    The LIB operations are simply names of object modules, with a prefix
  3321.    character that specifies the action to be taken: - to delete an object
  3322.    module from the library, * to extract a module and place it in a separate
  3323.    OBJ file, or + to add an object module or the entire contents of another
  3324.    library to the program library. The command prefixes can also be combined:
  3325.    -+ has the effect of replacing a module, while *- has the effect of
  3326.    extracting a module into a new file and then deleting it from the library.
  3327.  
  3328.    When the Librarian is invoked with its name alone, it will request the
  3329.    other information it needs interactively. For example:
  3330.  
  3331.    [C:\] LIB  <Enter>
  3332.  
  3333.    Microsoft (R) Library Manager  Version 3.11
  3334.    Copyright (C) Microsoft Corp 1983-1988. All rights reserved.
  3335.  
  3336.    Library name:  SLIBC  <Enter>
  3337.    Operations: +VIDEO  <Enter>
  3338.    List file:  SLIBC.LST  <Enter>
  3339.    Output library:  SLIBC2  <Enter>
  3340.  
  3341.    [C:\]
  3342.  
  3343.    In this case, the object module VIDEO.OBJ was added to the library
  3344.    SLIBC.LIB, a library table of contents was written into the file SLIB.LST,
  3345.    and the resulting new library was named SLIBC2.LIB.
  3346.  
  3347.    The Library Manager can also be run with a command line as follows:
  3348.  
  3349.      LIB library [operations],[list],[newlibrary]
  3350.  
  3351.    For example, the following command line is equivalent to the preceding
  3352.    interactive session:
  3353.  
  3354.    [C:\] LIB SLIBC +VIDEO,SLIBC.LST,SLIBC2  <Enter>
  3355.  
  3356.    As with the other Microsoft utilities, a semicolon at the end of the
  3357.    command line causes the default responses to be used for any parameters
  3358.    that are omitted.
  3359.  
  3360.    Like the Linker, the Librarian is capable of accepting its commands from a
  3361.    response file. The contents of the file are simply lines of text that
  3362.    correspond exactly to the responses you would give LIB interactively.
  3363.    Specify the name of the response file on the command line with a leading @
  3364.    character:
  3365.  
  3366.      LIB @filename
  3367.  
  3368.    LIB has only three options: /I (/IGNORECASE), /N (/NOIGNORECASE), and
  3369.    /PAGESIZE. Using /NOIGNORECASE causes LIB to regard symbols that differ
  3370.    only in the case of their component letters as distinct. (The default is
  3371.    to ignore case.)
  3372.  
  3373.    The /PAGESIZE switch is used in the following form:
  3374.  
  3375.      /PAGESIZE:number
  3376.  
  3377.    It is placed immediately after the library filename. Specify the library
  3378.    page size in bytes as a power of 2 between 16 and 32,768 (16, 32, 64...);
  3379.    the default is 16 bytes. The page size defines the size of a unit of
  3380.    allocation space for a given library. Because the index to a library is
  3381.    always a fixed number of pages, setting a larger page size allows you to
  3382.    store more object modules in that library; it will, on the other hand,
  3383.    result in more unused space within the file.
  3384.  
  3385.  
  3386.  Using BIND and API.LIB
  3387.  
  3388.    The BIND utility converts a protected mode segmented executable file into
  3389.    a file that can be executed in either protected mode or real mode. BIND
  3390.    accomplishes this somewhat magical feat by appending a real mode loader to
  3391.    the program, along with a real mode routine specific to each API service
  3392.    that it uses. When the file is used in real mode, the API-specific
  3393.    procedures remap the API call parameters into MS-DOS or ROM BIOS function
  3394.    calls. When the file is loaded in protected mode, the real mode loader and
  3395.    other routines appended by BIND are simply ignored.
  3396.  
  3397.    The BIND utility can process any protected mode program. However, for the
  3398.    file to be executed successfully, it must meet the following conditions:
  3399.  
  3400.    ■  The program cannot use any OS/2 kernel services that are not members of
  3401.       the Family API when it is running in real mode.
  3402.  
  3403.    ■  The entire program and the code appended to it by BIND must fit into
  3404.       the available "conventional" memory space (below 640 KB).
  3405.  
  3406.    BIND is command line driven──it does not have an interactive mode, nor can
  3407.    it handle response files. The BIND utility is invoked with an entry of the
  3408.    following form:
  3409.  
  3410.      BIND infile [file.LIB...] [file.OBJ ...] [options]
  3411.  
  3412.    Under normal conditions, the two libraries API.LIB and OS2.LIB must be
  3413.    specified in the command line. Other libraries, particularly dynlink
  3414.    reference libraries, might also be necessary. BIND does not search the
  3415.    directories named in the LIB environment variable, so the locations of
  3416.    libraries must be explicit.
  3417.  
  3418.    The default extension for infile is EXE; the output of BIND will receive
  3419.    the same filename unless otherwise specified with an options switch. The
  3420.    BIND options are listed in Figure 4-8 on the following page.
  3421.  
  3422.    Option               Description
  3423.    ──────────────────────────────────────────────────────────────────────────
  3424.    /o outfile           Specifies the name of the output EXE file (default =
  3425.                         same name as input EXE file)
  3426.  
  3427.    /n name(s)           Specifies one or more "protected mode only" functions
  3428.                         to be mapped to the BadDynLink function
  3429.  
  3430.    /n @filename         Specifies the name of a file containing one or more
  3431.                         "protected mode only" function names to be mapped to
  3432.                         the BadDynLink function
  3433.  
  3434.    /m                   Produces a map file named outfile.BM
  3435.    ──────────────────────────────────────────────────────────────────────────
  3436.  
  3437.    Figure 4-8.  Options for the BIND utility, version 1.1.
  3438.  
  3439.  
  3440.  Using the MAKE Utility
  3441.  
  3442.    The primary function of the MAKE utility is to compare dates of files and
  3443.    to carry out commands based on the result of that comparison. Because of
  3444.    this single, rather basic capability, MAKE can be used to maintain complex
  3445.    programs built from many modules. The dates of source, object, and
  3446.    executable files are simply compared in a logical sequence, and the
  3447.    assembler, compiler, linker, and other programming tools are invoked as
  3448.    appropriate.
  3449.  
  3450.    The MAKE utility processes a plain ASCII text file called, as you might
  3451.    expect, a "make" file. The utility is command line driven and is started
  3452.    with an entry of the following form:
  3453.  
  3454.      MAKE makefile [options]
  3455.  
  3456.    By convention, a make file has the same name as the executable file which
  3457.    is being maintained, but it takes no extension. The available MAKE options
  3458.    are listed in Figure 4-9.
  3459.  
  3460.    Option               Description
  3461.    ──────────────────────────────────────────────────────────────────────────
  3462.    /D                   Displays last modification date of each file as it is
  3463.                         processed
  3464.  
  3465.    /I                   Ignores exit ("return") codes returned by commands
  3466.                         and programs executed as a result of dependency
  3467.                         statements
  3468.  
  3469.    /N                   Displays commands that would be executed as a result
  3470.                         of dependency statements but does not execute those
  3471.                         commands
  3472.  
  3473.    /S                   Does not display commands as they are executed
  3474.  
  3475.    /X filename          Directs error messages from MAKE, or from any program
  3476.                         that MAKE runs, to the specified file; if filename is
  3477.                         a hyphen (-), error messages are directed to the
  3478.                         standard output
  3479.    ──────────────────────────────────────────────────────────────────────────
  3480.  
  3481.    Figure 4-9.  Options for the MAKE utility, version 4.07.
  3482.  
  3483.    A simple make file contains one or more dependency statements separated by
  3484.    blank lines. Each dependency statement can be followed by a list of OS/2
  3485.    commands in the following form:
  3486.  
  3487.      targetfile : sourcefile ...
  3488.              command
  3489.              command
  3490.              .
  3491.              .
  3492.              .
  3493.  
  3494.    If the date and time of any sourcefile are later than the targetfile, the
  3495.    accompanying list of commands is carried out. Comment lines, which begin
  3496.    with a # character, can be used freely. MAKE can also process inference
  3497.    rules and macro definitions; for further details on these advanced
  3498.    capabilities, see the Microsoft or IBM documentation.
  3499.  
  3500.  
  3501.  A Complete Example
  3502.  
  3503.    Let's put together everything we've learned about using the OS/2
  3504.    programming tools up to this point. The general process of creating an
  3505.    application program for OS/2, including use of the Resource Compiler (RC)
  3506.    to build a Presentation Manager application (not within the scope of this
  3507.    book), is illustrated in Figure 4-10 on the following page.
  3508.  
  3509.    Assume that we have the source for the HELLO program discussed in Chapter
  3510.    3 in the file HELLO.ASM, and assume that the definition file for the same
  3511.    program is named HELLO.DEF. Assume further that both files are located in
  3512.    the current directory. To assemble the source program into the relocatable
  3513.    object module HELLO.OBJ, producing a program listing in the file HELLO.LST
  3514.    and a cross-reference data file HELLO.CRF, we would enter the following
  3515.    command:
  3516.  
  3517.    [C:\] MASM HELLO,,HELLO,HELLO  <Enter>
  3518.  
  3519.    To convert the relocatable object file HELLO.OBJ into the executable file
  3520.    HELLO.EXE, using the module definition file HELLO.DEF and the import
  3521.    library OS2.LIB, and to create a load map in the file HELLO.MAP, we would
  3522.    enter the following:
  3523.  
  3524.    [C:\] LINK HELLO,,HELLO,OS2,HELLO  <Enter>
  3525.  
  3526.            ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┐
  3527.            │                      │
  3528.     ┌────────────┐        ┌────────────┐                │
  3529.     │ MASM source │        │ C or other  │
  3530.     │  code file  │        │ HLL source  │                │
  3531.     └──┬──────────┘        │  code file  │
  3532.        │ MASM              └────┬────────┘                │
  3533.        │    ┌───────────────────┘ Compiler
  3534.    ┌───────────┐         ┌─────────────┐                │
  3535.    │ Relocatable │         │ C or other  │
  3536.    │object module├──┐     │ HLL source  │                │
  3537.    │  file (OBJ) │   │     │  code file  │
  3538.    └──────┬──────┘   │     └────────────┘                │
  3539.           │ LIB      │         RC │
  3540.    ┌────────────┐   │            │      ┌──────────────┐ │
  3541.    │Object module│   │            │─────┤  Resources   │
  3542.    │library file ├──│            │      └──────────────┘ │
  3543.    │    (LIB)    │   │            │
  3544.    └─────────────┘   │     ┌──────┴──────┐                │
  3545.    ┌─────────────┐   │LINK │  Protected  │      Code View
  3546.    │   OS2.LIB   ├──├────│    mode     │─ ─ ─ ─ ─ ─ ─ ─ ┘
  3547.    └─────────────┘   │     │ application │
  3548.    ┌─────────────┐   │     └──────┬──────┘
  3549.    │Other import │   │            │      ┌──────────────┐
  3550.    │  libraries  ├──│            │─────┤    APLLIB    │
  3551.    └─────────────┘   │            │      └──────────────┘
  3552.    ┌─────────────┐   │     ┌──────┴──────┐
  3553.    │     HLL     │   │     │   Family    │
  3554.    │   runtime   ├──│     │ (dual mode) │
  3555.    │  libraries  │   │     │ application │
  3556.    └─────────────┘   │     └─────────────┘
  3557.    ┌─────────────┐   │
  3558.    │   Module    │   │
  3559.    │ definition  ├──┘
  3560.    │    file     │
  3561.    └─────────────┘
  3562.  
  3563.    Figure 4-10.  Creation of an OS/2 application program, proceeding from
  3564.    source code to executable file.
  3565.  
  3566.    Because our HELLO program uses only the OS/2 functions DosWrite and
  3567.    DosExit, both of which are members of the Family API, it can be converted
  3568.    to a Family application that runs in either DOS compatibility mode or
  3569.    protected mode. This conversion is performed with the BIND utility and
  3570.    API.LIB by entering the following command:
  3571.  
  3572.    [C:\] BIND HELLO.EXE \LIB\API.LIB \LIB\OS2.LIB <Enter>
  3573.  
  3574.    OS2.LIB must also be specified because its functions are exported by
  3575.    ordinal numbers rather than by name. Without reference to the ordinals in
  3576.    the OS2.LIB file, BIND can't match the functions imported in the HELLO.EXE
  3577.    header to API.LIB. Note also that BIND.EXE does not use the LIB
  3578.    environment variable, so the locations of API.LIB and OS2.LIB must be
  3579.    explicit (including drive name if necessary).
  3580.  
  3581.    The new HELLO.EXE file produced by BIND can execute in either real mode or
  3582.    protected mode on an 80286 or 80386 machine. To truly generalize this
  3583.    program and obtain a HELLO.EXE file that could run on any 80x86-based
  3584.    machine under MS-DOS or OS/2, we would have to replace all 80286-specific
  3585.    instructions in the source code with equivalent sequences that can run on
  3586.    an 8086/88. For example, we would need to replace the instruction
  3587.  
  3588.    push    msg_len
  3589.  
  3590.    with the instructions
  3591.  
  3592.    mov     ax,msg_len
  3593.    push    ax
  3594.  
  3595.    To locate the 80286-specific instructions in a program, simply remove the
  3596.    .286 directive from the source file and reassemble it; each instruction
  3597.    that does not run on an 8086/88 processor is then flagged as an error.
  3598.  
  3599.    To automate the entire process we have described, we could create a make
  3600.    file named HELLO (with no extension) that contains the following:
  3601.  
  3602.    HELLO.OBJ : HELLO.ASM
  3603.            MASM /T HELLO,,HELLO,HELLO
  3604.  
  3605.    HELLO.EXE : HELLO.OBJ HELLO.DEF
  3606.            LINK HELLO,,HELLO,OS2,HELLO
  3607.            BIND HELLO.EXE \LIB\API.LIB \LIB\OS2.LIB
  3608.  
  3609.    Then, whenever we change either HELLO.ASM or HELLO.DEF and want to rebuild
  3610.    the executable HELLO.EXE file, we need only enter the following:
  3611.  
  3612.    [C:\] MAKE HELLO  <Enter>
  3613.  
  3614.    Note that the /T switch in the MASM command line suppresses all messages
  3615.    unless errors are encountered during assembly.
  3616.  
  3617.  
  3618.  
  3619.  ────────────────────────────────────────────────────────────────────────────
  3620.  SECTION 2  PROGRAMMING THE USER INTERFACE
  3621.  ────────────────────────────────────────────────────────────────────────────
  3622.  
  3623.  
  3624.  
  3625.  ────────────────────────────────────────────────────────────────────────────
  3626.  Chapter 5  Keyboard and Mouse Input
  3627.  
  3628.    The two primary means of user input in OS/2 are the keyboard and mouse.
  3629.    Keyboard support is, of course, always available; the base keyboard driver
  3630.    is always loaded during system initialization. In contrast, loading of the
  3631.    mouse driver is optional; the user must select the appropriate mouse
  3632.    driver by adding a DEVICE directive to the system's CONFIG.SYS file. The
  3633.    OS/2 retail package includes drivers for several brands of mice, including
  3634.    all models of the Microsoft Mouse.
  3635.  
  3636.  
  3637.  Keyboard Methods and Modes
  3638.  
  3639.    A Kernel application can obtain input from the keyboard in three ways:
  3640.  
  3641.    ■  Kbd (keyboard subsystem) API calls
  3642.  
  3643.    ■  DosRead with the predefined handle for the standard input (0)
  3644.  
  3645.    ■  DosRead with a handle obtained by opening logical device CON or KBD$
  3646.  
  3647.    The Kbd functions provide the most flexibility in keyboard management.
  3648.    Functionally, they are a superset of the ROM BIOS keyboard driver (Int
  3649.    16H) calls that real mode DOS applications commonly use.
  3650.  
  3651.    Ordinarily, the second method, calling DosRead with the standard input
  3652.    handle, is used only by applications──such as filters, compilers, and
  3653.    linkers──that process simple text streams and do not require rapid and
  3654.    flexible interaction with the user. The user can easily redirect the
  3655.    standard input to a file or character device other than the keyboard.
  3656.  
  3657.    The last method, DosRead with a handle obtained by an explicit open of CON
  3658.    or KBD$, is rarely used. It is appropriate for a program that processes
  3659.    simple text streams but must circumvent redirection, or that needs to use
  3660.    DosRead and also perform DosDevIOCtl calls to the keyboard driver.
  3661.  
  3662.    Each screen group has its own default logical keyboard, and the driver
  3663.    maintains a distinct state for each keyboard. (Additional logical
  3664.    keyboards can be created by applications, as will be discussed later in
  3665.    the chapter.) The keyboard state affects the behavior of all the keyboard
  3666.    input functions and includes the status of the shift keys and toggles, the
  3667.    turnaround character (logical end-of-line character, usually the Enter
  3668.    key), an echo flag, and an input mode.
  3669.  
  3670.    The input mode is the element of the keyboard state that is most likely to
  3671.    concern the application programmer. A particular logical keyboard is
  3672.    either in ASCII ("cooked") mode or binary ("raw") mode at any given time.
  3673.  
  3674.    In ASCII mode (the default condition), the key combinations Ctrl-C,
  3675.    Ctrl-Break, Ctrl-S, Ctrl-P, and Ctrl-PrtSc are detected by the operating
  3676.    system and receive special treatment. The echoing of characters to the
  3677.    display and the handling of "extended" keys and the turnaround character
  3678.    depend on the input function being used.
  3679.  
  3680.    In binary mode, characters are never echoed to the display. The turnaround
  3681.    character, extended keys, and special key combinations Ctrl-C, Ctrl-Break,
  3682.    Ctrl-S, Ctrl-P, and Ctrl-PrtSc are passed through to the application
  3683.    unchanged.
  3684.  
  3685.    ──────────────────────────────────────────────────────────────────────────
  3686.    NOTE:
  3687.      The special characters Ctrl-Esc, Alt-Esc, Ctrl-Alt-Del,
  3688.      Ctrl-Alt-NumLock, and PrtSc are always trapped by the operating system,
  3689.      regardless of the input mode, and an application cannot read or
  3690.      intercept them.
  3691.    ──────────────────────────────────────────────────────────────────────────
  3692.  
  3693.  
  3694.  Input with Kbd Functions
  3695.  
  3696.    The Kbd functions provide a variety of keyboard input and control services
  3697.    (Figure 5-1). These range in complexity from single character input to
  3698.    installation of custom code pages (tables that map the keyboard to
  3699.    specific character codes).
  3700.  
  3701. ╓┌─┌────────────────┌────────────────────────────────────────────────────────╖
  3702.    Function         Description
  3703.    ──────────────────────────────────────────────────────────────────────────
  3704.    Keyboard Input
  3705.    KbdCharIn       Reads character without echo
  3706.    KbdPeek         Returns next character (if any) without removing it from
  3707.                     the input buffer
  3708.    KbdStringIn     Reads a buffered line with echo and editing capability
  3709.    KbdFlushBuffer  Discards all waiting characters
  3710.    KbdXlate        Translates scan code and shift states to character
  3711.  
  3712.    Logical Keyboards
  3713.    KbdOpen         Creates logical keyboard
  3714.    KbdGetFocus     Binds logical keyboard to physical keyboard
  3715.    KbdFreeFocus    Releases logical to physical keyboard bonding
  3716.    KbdClose        Destroys logical keyboard
  3717.  
  3718.    Keyboard Status
  3719.    Function         Description
  3720.    ──────────────────────────────────────────────────────────────────────────
  3721.   Keyboard Status
  3722.    KbdGetCp        Returns keyboard code page ID
  3723.    KbdGetStatus    Returns keyboard state and flags
  3724.  
  3725.    Keyboard Configuration
  3726.    KbdSetCp        Selects keyboard code page
  3727.    KbdSetCustXt    Installs custom code page
  3728.    KbdSetStatus    Configures keyboard state and flags
  3729.    KbdSynch        Synchronizes keyboard access
  3730.  
  3731.    Register/Deregister Keyboard Subsystem
  3732.    KbdRegister     Registers alternative keyboard subsystem
  3733.    KbdDeRegister   Deregisters alternative keyboard subsystem
  3734.    ──────────────────────────────────────────────────────────────────────────
  3735.  
  3736.  
  3737.    Figure 5-1.  The Kbd API at a glance.
  3738.  
  3739.    The prototypal Kbd input operation is KbdCharIn, which returns an ASCII
  3740.    character code, a scan code, a time stamp, and a word of flags for the
  3741.    keyboard shift keys and toggles. The character is not echoed to the
  3742.    display. Extended keys return a 00H or an E0H in the ASCII character
  3743.    field; the application must inspect the scan code to identify the key.
  3744.    KbdCharIn has a wait/no-wait option, which allows the caller to specify
  3745.    whether the function should block until a key is ready or return
  3746.    immediately if no key is available.
  3747.  
  3748.    A related function, KbdPeek, returns the same information as KbdCharIn but
  3749.    does not remove the character from the keyboard input buffer. It allows an
  3750.    application to test keyboard status and to "look ahead" by one character.
  3751.    Avoid using KbdPeek to "poll" the keyboard, however; such polling can
  3752.    severely degrade system performance.
  3753.  
  3754.    KbdStringIn provides buffered line input. It is called with the address of
  3755.    a buffer, a wait/no-wait parameter, and the address of a structure that
  3756.    contains the buffer length and the amount of data already in the buffer
  3757.    which can be used as an editing template (if any). Upon return, the
  3758.    function places the actual number of characters read in the same
  3759.    structure.
  3760.  
  3761.    If KbdStringIn is called and the keyboard is in ASCII mode, the editing
  3762.    keys are active and all other extended keys are ignored and thrown away.
  3763.    Each character is echoed to the display unless the echo flag is off. Tabs
  3764.    are expanded to spaces using 8-column tab stops when they are echoed,
  3765.    although they are left as the code 09H in the buffer. Input is terminated
  3766.    by the Enter key (placed in the buffer as the single character 0DH) or by
  3767.    the buffer's reaching capacity: When one less than the requested length
  3768.    has been read, additional keystrokes are discarded and a warning beep is
  3769.    sounded until the user presses Enter. The no-wait option is not available
  3770.    in ASCII mode.
  3771.  
  3772.    In binary mode, KbdStringIn returns when the buffer is full, keys are not
  3773.    echoed to the display, and the editing keys are not active. Extended keys
  3774.    are placed in the buffer as a 2-byte sequence: a 00H or an E0H byte
  3775.    followed by the scan code. If a program calls KbdStringIn in binary mode
  3776.    with the no-wait option, the function returns immediately with whatever
  3777.    keys are already waiting (up to the maximum requested).
  3778.  
  3779.    Figures 5-2 and 5-3 illustrate the use of the KbdCharIn and KbdStringIn
  3780.    calls.
  3781.  
  3782.    ──────────────────────────────────────────────────────────────────────────
  3783.                                    ; this structure receives
  3784.                                    ; information from KbdCharIn
  3785.    kbdinfo db      0               ; ASCII character code
  3786.            db      0               ; scan code
  3787.            db      0               ; character status
  3788.            db      0               ; NLS shift status
  3789.            dw      0               ; keyboard shift status
  3790.            dd      0               ; time stamp
  3791.  
  3792.            .
  3793.            .
  3794.            .
  3795.                                    ; read a character
  3796.            push    ds              ; address of structure
  3797.            push    offset DGROUP:kbdinfo
  3798.            push    0               ; 0 = wait for character
  3799.                                    ; 1 = no wait
  3800.            push    0               ; default keyboard handle
  3801.            call    KbdCharIn       ; transfer to OS/2
  3802.            or      ax,ax           ; did read succeed?
  3803.            jnz     error           ; jump if function failed
  3804.            .
  3805.            .
  3806.            .
  3807.    ──────────────────────────────────────────────────────────────────────────
  3808.  
  3809.    Figure 5-2.  Reading a character from the keyboard with KbdCharIn. If the
  3810.    no-wait option is used, bit 6 of the character status byte is set if a
  3811.    character was obtained; bit 6 is clear if no character was ready.
  3812.  
  3813.    ──────────────────────────────────────────────────────────────────────────
  3814.    buffer  db      80 dup (0)      ; keyboard data goes here
  3815.  
  3816.    buflen  dw      80              ; contains length of buffer
  3817.            dw      0               ; contains length of data in
  3818.                                    ; buffer for editing and
  3819.                                    ; receives length of data
  3820.            .
  3821.            .
  3822.            .
  3823.                                    ; indicate nothing is in
  3824.                                    ; buffer to be edited...
  3825.            mov     word ptr buflen+2,0
  3826.  
  3827.            push    ds              ; input buffer address
  3828.            push    offset DGROUP:buffer
  3829.            push    ds              ; control structure address
  3830.            push    offset DGROUP:buflen
  3831.            push    0               ; 0 = wait for data
  3832.                                    ; 1 = no wait
  3833.            push    0               ; default keyboard handle
  3834.            call    KbdStringIn     ; transfer to OS/2
  3835.            or      ax,ax           ; did read succeed?
  3836.            jnz     error           ; jump if function failed
  3837.            .
  3838.            .
  3839.            .
  3840.    ──────────────────────────────────────────────────────────────────────────
  3841.  
  3842.    Figure 5-3.  Reading a buffered line from the keyboard with KbdStringIn.
  3843.    Note that the second word of the buflen structure controls whether the
  3844.    user can edit data already in the buffer (typically from the previous call
  3845.    to KbdStringIn).
  3846.  
  3847.  
  3848.  Input with DosRead
  3849.  
  3850.    The general-purpose function DosRead accepts a handle, a buffer address, a
  3851.    buffer length, and the address of a variable. The handle must be the
  3852.    system's predefined standard input handle, a handle to a file, device, or
  3853.    pipe inherited from the process's parent, or a handle obtained from a
  3854.    successful DosOpen operation. When DosRead returns, the requested data is
  3855.    in the buffer, and the actual number of bytes read is in the specified
  3856.    variable. Figure 5-4 on the following page contains an example of
  3857.    buffered keyboard input using DosRead.
  3858.  
  3859.    When DosRead is called with the standard input handle (and the standard
  3860.    input has not been redirected) or with a handle that is opened to device
  3861.    KBD$, the kernel converts the DosRead into a call to KbdStringIn. However,
  3862.    such a DosRead request behaves somewhat differently from KbdStringIn in
  3863.    two respects.
  3864.  
  3865.    ──────────────────────────────────────────────────────────────────────────
  3866.    stdin   equ     0               ; standard input handle
  3867.  
  3868.    buflen  equ     80              ; length of input buffer
  3869.  
  3870.  
  3871.    buffer  db      buflen dup (0)  ; keyboard data goes here
  3872.  
  3873.    rdlen   dw      0               ; receives character count
  3874.  
  3875.            .
  3876.            .
  3877.            .
  3878.            push    stdin           ; standard input handle
  3879.            push    ds              ; input buffer address
  3880.            push    offset DGROUP:buffer
  3881.            push    buflen          ; maximum characters to read
  3882.            push    ds              ; receives actual length
  3883.            push    offset DGROUP:rdlen
  3884.            call    DosRead         ; transfer to OS/2
  3885.            or      ax,ax           ; did read succeed?
  3886.            jnz     error           ; jump if function failed
  3887.            .
  3888.            .
  3889.            .
  3890.    ──────────────────────────────────────────────────────────────────────────
  3891.  
  3892.    Figure 5-4.  Reading a buffered line from the keyboard with DosRead. When
  3893.    DosRead returns, rdlen contains the number of characters actually
  3894.    transferred to the buffer.
  3895.  
  3896.    First, in ASCII mode, DosRead translates the Enter key into a 2-character
  3897.    sequence in the buffer: a carriage return (0DH) followed by a linefeed
  3898.    (0AH). In binary mode, DosRead leaves the Enter key in the buffer as a
  3899.    carriage return only.
  3900.  
  3901.    The other difference is more subtle. Regardless of the buffer length
  3902.    specified in the DosRead call, the kernel calls KbdStringIn with a buffer
  3903.    size of 253 characters. This means that the keyboard will not begin to
  3904.    beep and ignore keystrokes until the user has entered 252 keys. However,
  3905.    the number of characters specified in the DosRead call is used as the
  3906.    maximum for the input returned to the application, and any excess
  3907.    characters (possibly including the carriage return and linefeed) are
  3908.    discarded.
  3909.  
  3910.    Although keyboard input with DosRead is appropriate for filters──which
  3911.    need the capability of redirectable input──this method is not recommended
  3912.    for interactive applications. The Kbd calls are preferred because they are
  3913.    more efficient, allow closer control of the keyboard, and return more
  3914.    detailed information.
  3915.  
  3916.    If you want to take full advantage of the Kbd interface while allowing
  3917.    input redirection in your application, call DosQHandType with the standard
  3918.    input handle to determine whether the standard input has been redirected.
  3919.    If DosQHandType returns the code for a character device and a device
  3920.    driver attribute word with bit 0 set (1), then the standard input handle
  3921.    has not been redirected, and the application can use the Kbd calls
  3922.    throughout its execution. Otherwise, the application should use DosRead.
  3923.  
  3924.  
  3925.  Keyboard Status and Control
  3926.  
  3927.    Application programs can query or control many aspects of the logical
  3928.    keyboard state, including the code page, the mode (ASCII or binary), the
  3929.    status of shift keys and toggles, the automatic echoing of keys to the
  3930.    display by KbdStringIn, and the turnaround key. Kbd functions are
  3931.    available to inspect or to alter each of these parameters. A similar set
  3932.    of DosDevIOCtl functions is available and can be used by applications that
  3933.    use the DosRead model for keyboard input (Figure 5-5).
  3934.  
  3935. ╓┌─┌────────────────────┌────────────────────────────────────────────────────╖
  3936.    Function Number      Operation
  3937.    ──────────────────────────────────────────────────────────────────────────
  3938.    50H                 Set code page
  3939.    51H                 Set input mode
  3940.    52H                 Set interim character flags
  3941.    53H                 Set shift state
  3942.    54H                 Set typematic rate and delay
  3943.    55H                 Notify change of foreground session
  3944.    56H                 Select Task Manager hot key
  3945.    57H                 Bind logical keyboard to physical keyboard
  3946.    58H                 Set code page ID
  3947.    5CH                 Set national language support and custom code page
  3948.    5DH                 Create logical keyboard
  3949.    5EH                 Destroy logical keyboard
  3950.    Function Number      Operation
  3951.    ──────────────────────────────────────────────────────────────────────────
  3952.   5EH                 Destroy logical keyboard
  3953.    71H                 Get input mode
  3954.    72H                 Get interim character flags
  3955.    73H                 Get shift state
  3956.    74H                 Read character
  3957.    75H                 Peek character
  3958.    76H                 Get Task Manager hot key
  3959.    77H                 Get keyboard type
  3960.    78H                 Get code page ID
  3961.    79H                 Translate scan code to ASCII
  3962.    ──────────────────────────────────────────────────────────────────────────
  3963.  
  3964.  
  3965.    Figure 5-5.  DosDevIOCtl Category 4 functions for keyboard status and
  3966.    control.
  3967.  
  3968.    The most common need for keyboard control in an application is undoubtedly
  3969.    to select ASCII or binary mode. Programs that use the Kbd calls can get or
  3970.    set the mode with KbdGetStatus and KbdSetStatus. These functions use a
  3971.    common data structure that contains much other information, so a program
  3972.    should call KbdGetStatus first to fill the structure with valid data. The
  3973.    ASCII/binary mode bits can then be modified and the structure written back
  3974.    to the system with KbdSetStatus (Figure 5-6).
  3975.  
  3976.    ──────────────────────────────────────────────────────────────────────────
  3977.                                    ; keyboard info structure
  3978.    kdbinfo dw      10              ; length of structure
  3979.            dw      0               ; various mode bits
  3980.            dw      0               ; logical end-of-line character
  3981.            dw      0               ; interim character flags
  3982.            dw      0               ; keyboard shift states
  3983.  
  3984.    oldmode dw      0               ; previous keyboard mode
  3985.  
  3986.            .
  3987.            .
  3988.            .
  3989.                                    ; get keyboard status...
  3990.            push    ds              ; address of structure
  3991.            push    offset DGROUP:kbdinfo
  3992.            push    0               ; default keyboard handle
  3993.            call    KbdGetStatus    ; transfer to OS/2
  3994.            or      ax,ax           ; did function succeed?
  3995.            jnz     error           ; jump if function failed
  3996.  
  3997.            mov     ax,kbdinfo+2    ; save current input mode so
  3998.            mov     oldmode,ax      ; we can restore it later...
  3999.  
  4000.                                    ; turn off ASCII bit and
  4001.                                    ; turn on binary mode bit
  4002.            and     word ptr kbdinfo+2,0fff7h
  4003.            or      word ptr kbdinfo+2,4
  4004.  
  4005.                                    ; now set keyboard status...
  4006.            push    ds              ; address of structure
  4007.            push    offset DGROUP:kbdinfo
  4008.            push    0               ; default keyboard handle
  4009.            call    KbdSetStatus    ; transfer to OS/2
  4010.            or      ax,ax           ; did function succeed?
  4011.            jnz     error           ; jump if function failed
  4012.            .
  4013.            .
  4014.            .
  4015.    ──────────────────────────────────────────────────────────────────────────
  4016.  
  4017.    Figure 5-6.  Putting the default logical keyboard into binary mode with
  4018.    KbdGetStatus and KbdSetStatus so that the application can read Ctrl-C and
  4019.    other special characters. The original mode is saved so that it can be
  4020.    restored later.
  4021.  
  4022.    Programs that use DosRead for keyboard input can manipulate the mode with
  4023.    DosDevIOCtl Category 4 Functions 71H (Get Mode) and 51H (Set Mode). These
  4024.    functions can be used only with a handle obtained by opening KBD$ with
  4025.    DosOpen; if you use them with the handle for the standard input or with a
  4026.    handle from a DosOpen of CON, the system returns error code 22 Bad Command
  4027.    (Figure 5-7).
  4028.  
  4029.    ──────────────────────────────────────────────────────────────────────────
  4030.    kbdname db      'KBD$',0        ; keyboard device name
  4031.    khandle dw      0               ; keyboard handle
  4032.  
  4033.    oldmode db      0               ; previous keyboard mode
  4034.  
  4035.    newmode db      80h             ; new keyboard mode
  4036.                                    ; 0 = ASCII, 80H = binary
  4037.  
  4038.            .
  4039.            .
  4040.            .
  4041.                                    ; get keyboard handle...
  4042.            push    ds              ; address of device name
  4043.            push    offset DGROUP:kbdname
  4044.            push    ds              ; receives handle
  4045.            push    offset DGROUP:khandle
  4046.            push    ds              ; receives DosOpen action
  4047.            push    offset DGROUP:kaction
  4048.            push    0               ; file size (not applicable)
  4049.            push    0
  4050.            push    0               ; file attribute (ditto)
  4051.            push    1               ; action = open, no create
  4052.            push    42h             ; mode = r/w, deny none
  4053.            push    0               ; reserved DWORD 0
  4054.            push    0
  4055.            call    DosOpen         ; transfer to OS/2
  4056.            or      ax,ax           ; didopen succeed?
  4057.            jnz     error           ; jump if function failed
  4058.            .
  4059.            .
  4060.            .
  4061.                                    ; get current input mode so
  4062.                                    ; we can restore it later...
  4063.            push    ds              ; data packet pointer
  4064.            push    offset DGROUP:oldmode
  4065.            push    0               ; parameter packet addr. (none)
  4066.            push    0
  4067.            push    71h             ; function code
  4068.            push    4               ; category code
  4069.            push    khandle         ; device handle
  4070.            call    DosDevIOCtl     ; transfer to OS/2
  4071.            or      ax,ax           ; did function succeed?
  4072.            jnz     error           ; jump if function failed
  4073.  
  4074.                                    ; now set binary mode...
  4075.            push    0               ; data packet pointer (none)
  4076.            push    0
  4077.            push    ds              ; parameter packet pointer
  4078.            push    offset DGROUP:newmode
  4079.            push    51h             ; function code
  4080.            push    4               ; category code
  4081.            push    khandle         ; device handle
  4082.            call    DosDevIOCtl     ; transfer to OS/2
  4083.            or      ax,ax           ; did function succeed?
  4084.            jnz     error           ; jump if function failed
  4085.            .
  4086.            .
  4087.            .
  4088.    ──────────────────────────────────────────────────────────────────────────
  4089.  
  4090.    Figure 5-7.  Putting the keyboard into binary mode with DosDevIOCtl
  4091.    Category 4 Function 51H so that the application can read Ctrl-C and other
  4092.    special characters. The original mode is obtained first with Category 4
  4093.    Function 71H so that it can be restored later. Note that DosDevIOCtl
  4094.    cannot be used with the standard input handle.
  4095.  
  4096.  
  4097.  Using Logical Keyboards
  4098.  
  4099.    Each screen group has a default logical keyboard that any process can
  4100.    access without further preliminaries using the Kbd calls and keyboard
  4101.    handle 0 or using DosRead with the standard input handle (0). When a user
  4102.    brings a screen group to the foreground, its logical keyboard is "bound"
  4103.    to the physical keyboard, and the processes in that group can read any
  4104.    keys the user presses. When a screen group is in the background, processes
  4105.    in the group that attempt to read from the keyboard are blocked.
  4106.  
  4107.    Processes that use the default logical keyboard are vulnerable to
  4108.    interference by other processes in the same group that also use the
  4109.    default. For example, if a process is interacting with the user and
  4110.    performing key-by-key input, and another process also requests a key, the
  4111.    input received by both programs will be unpredictable.
  4112.  
  4113.    To circumvent this problem, a program can create a new logical keyboard
  4114.    for its screen group with KbdOpen, bind that logical keyboard to the
  4115.    physical keyboard with KbdGetFocus, and then perform its Kbd input
  4116.    operations with the new handle obtained from KbdOpen (Figure 5-8).
  4117.    Keyboard input operations by other processes in the same screen group that
  4118.    use the handle for the default keyboard (0) or any other logical keyboard
  4119.    will be blocked while a KbdGetFocus is in effect.
  4120.  
  4121.    When the process finishes obtaining input, it can use KbdFreeFocus to
  4122.    sever the bond between its logical keyboard and the physical keyboard,
  4123.    thereby allowing other processes to proceed with Kbd operations. The
  4124.    logical keyboard created with KbdOpen can be destroyed with KbdClose; in
  4125.    any event, it is destroyed when the process terminates as part of the
  4126.    kernel's normal cleanup.
  4127.  
  4128.    ──────────────────────────────────────────────────────────────────────────
  4129.    khandle dw      0               ; receives handle for
  4130.                                    ; new logical keyboard
  4131.            .
  4132.            .
  4133.            .
  4134.                                    ; create a logical keyboard
  4135.            push    ds              ; receives keyboard handle
  4136.            push    offset DGROUP:khandle
  4137.            call    KbdOpen         ; transfer to OS/2
  4138.            or      ax,ax           ; did open succeed?
  4139.            jnz     error           ; jump if function failed
  4140.            .
  4141.            .
  4142.            .
  4143.                                    ; ready for input, get
  4144.                                    ; keyboard focus...
  4145.            push    0               ; 0 = wait if necessary
  4146.            push    khandle         ; keyboard handle
  4147.            call    KbdGetFocus     ; transfer to OS/2
  4148.            or      ax,ax           ; did we get focus?
  4149.            jnz     error           ; jump if function failed
  4150.  
  4151.            .
  4152.            .                       ; perform input here...
  4153.            .
  4154.                                    ; done with input, release
  4155.                                    ; keyboard focus...
  4156.            push    khandle         ; keyboard handle
  4157.            call    KbdFreeFocus    ; transfer to OS/2
  4158.            or      ax,ax           ; did release succeed?
  4159.            jnz     error           ; jump if function failed
  4160.            .
  4161.            .
  4162.            .
  4163.                                    ; logical keyboard no longer
  4164.                                    ; needed, destroy it...
  4165.            push    khandle         ; keyboard handle
  4166.            call    KbdClose        ; transfer to OS/2
  4167.            .
  4168.            .
  4169.            .
  4170.    ──────────────────────────────────────────────────────────────────────────
  4171.  
  4172.    Figure 5-8.  Creating and using a new logical keyboard to prevent keyboard
  4173.    interference by other processes in the same screen group.
  4174.  
  4175.  
  4176.  Keyboard Subsystems and Drivers
  4177.  
  4178.    The OS/2 substructure for the keyboard support visible to an application
  4179.    is divided among several modules (Figure 5-9). The character device
  4180.    driver KBD$ (loaded from the file KBD01.SYS or KBD02.SYS) services
  4181.    keyboard interrupts, reads keyboard scan codes from the keyboard
  4182.    controller, and interprets those scan codes as characters according to the
  4183.    code page for the current screen group. KBD$ also cooperates with the
  4184.    kernel's monitor dispatcher to create and maintain device monitor chains
  4185.    for each screen group. The keyboard driver runs in kernel mode (ring 0).
  4186.  
  4187.    The dynlink library BKSCALLS.DLL contains the Basic Keyboard Subsystem,
  4188.    which implements the Kbd services. BKSCALLS communicates with the KBD$
  4189.    driver through DosDevIOCtl calls only; it does not issue DosRead or
  4190.    DosWrite calls.
  4191.  
  4192.    The dynlink library KBDCALLS.DLL is called the "keyboard router." It
  4193.    contains the entry points for the Kbd API and passes the calls onward to
  4194.    the appropriate subsystem (such as BKSCALLS) on a per-screen-group basis.
  4195.    Both BKSCALLS and KBDCALLS run in user mode and thus access the keyboard
  4196.    hardware only indirectly.
  4197.  
  4198.    Although BKSCALLS is the default keyboard subsystem for each screen group,
  4199.    part or all of its services can be replaced on a per-screen-group basis.
  4200.    To activate a new subsystem, call KbdRegister with a dynlink library name,
  4201.    an entry point name, and a set of flags that indicate which Kbd services
  4202.    the module supports; those functions that the module does not support
  4203.    continue to be passed to BKSCALLS. A call to KbdDeRegister deactivates the
  4204.    previously registered subsystem and causes all Kbd processing to revert to
  4205.    BKSCALLS.
  4206.  
  4207.    ┌──────────────┐
  4208.    │ Application  │────────────────┐
  4209.    └─────────────┘                 │
  4210.           │ Kbd API calls           │
  4211.    ┌─────────────┐                 │
  4212.    │ KDBCALLS.DLL │                 │
  4213.    └─────────────┘                 │ DosDevIOCtl
  4214.           │ Private call interface  │ or
  4215.    ┌─────────────┐                 │ DosRead
  4216.    │ BKSCALLS.DLL │                 │
  4217.    └─────────────┘                 │
  4218.           │ DosDevIOCtl             │
  4219.    ┌─────────────┐                 │
  4220.    │ OS/2 kernel  │────────────────┘
  4221.    └─────────────┘
  4222.           │ Request packet interface
  4223.    ┌─────────────┐
  4224.    │ KBD$ device  │
  4225.    │    driver    │
  4226.    └─────────────┘
  4227.           │ Hardware-dependent I/O operations
  4228.    ┌─────────────┐
  4229.    │   Keyboard   │
  4230.    │  controller  │
  4231.    └──────────────┘
  4232.  
  4233.    Figure 5-9.  The system modules responsible for keyboard support. The role
  4234.    of device monitors is not shown here.
  4235.  
  4236.  
  4237.  Keyboard Monitors
  4238.  
  4239.    Several OS/2 character device drivers, including the keyboard driver,
  4240.    support a special class of applications called device monitors. A process
  4241.    that registers with the keyboard driver as a monitor can inspect and
  4242.    modify the raw keyboard data stream on a per-screen-group basis. Multiple
  4243.    applications can be registered concurrently as keyboard monitors and can
  4244.    even specify where they want to be located in the chain of all monitors.
  4245.    This facility makes it easy to write "well-behaved" keyboard enhancers,
  4246.    macro processors, and popup utilities.
  4247.  
  4248.    In Chapter 18, keyboard device monitors are discussed in more detail, and
  4249.    a working sample program is presented. For now, simply note the following:
  4250.  
  4251.    ■  A monitor receives a data packet for each keyboard event (press or
  4252.       release of any key); the packet contains the original scan code, the
  4253.       translated ASCII code (if any), a time stamp, the keyboard shift state,
  4254.       and a word of flags.
  4255.  
  4256.    ■  A monitor can consume, modify, or insert keyboard data packets.
  4257.  
  4258.    ■  A monitor is responsible for passing the keyboard data through its
  4259.       monitor buffers promptly to avoid interfering with other processes in
  4260.       the screen group it is monitoring. (A dedicated thread with
  4261.       time-critical priority is usually created for this purpose.)
  4262.  
  4263.    ■  A process can register as a keyboard monitor for a screen group other
  4264.       than the one in which it is running and as a monitor for multiple
  4265.       screen groups.
  4266.  
  4267.  
  4268.  Mouse Input
  4269.  
  4270.    In keeping with its emphasis on graphical user interfaces and pointing
  4271.    devices, OS/2 provides applications with a variety of mouse support
  4272.    functions (Figures 5-10 and 5-11). The full list of Mou API services
  4273.    might appear intimidating, but the average application needs only a few of
  4274.    them.
  4275.  
  4276. ╓┌─┌────────────────────┌────────────────────────────────────────────────────╖
  4277.    Function             Description
  4278.    ──────────────────────────────────────────────────────────────────────────
  4279.    Enable/Disable Mouse Support
  4280.    MouOpen             Obtains mouse handle
  4281.    MouClose            Releases mouse handle
  4282.  
  4283.    Mouse Event Messages
  4284.    MouReadEventQue     Reads mouse event record (position and button status)
  4285.    MouGetNumQueEl      Returns number of waiting mouse event records
  4286.    Function             Description
  4287.    ──────────────────────────────────────────────────────────────────────────
  4288.   MouGetNumQueEl      Returns number of waiting mouse event records
  4289.    MouFlushQue         Discards any waiting event records
  4290.  
  4291.    Mouse Pointer Control
  4292.    MouGetPtrPos        Returns mouse pointer position
  4293.    MouSetPtrPos        Sets mouse pointer position
  4294.    MouGetPtrShape      Returns mouse pointer shape
  4295.    MouSetPtrShape      Defines mouse pointer shape
  4296.    MouDrawPtr          Displays mouse pointer
  4297.    MouRemovePtr        Defines mouse pointer exclusion area
  4298.  
  4299.    Mouse Driver Status
  4300.    MouGetDevStatus     Returns mouse driver status flags
  4301.    MouGetEventMask     Returns mouse driver event mask
  4302.    MouGetHotKey        Returns mouse button(s) defined as system hot key
  4303.    MouGetNumButtons    Returns number of mouse buttons
  4304.    MouGetNumMickeys    Returns number of mickeys per centimeter
  4305.    MouGetScaleFact     Returns scaling factors for mouse coordinates
  4306.  
  4307.    Function             Description
  4308.    ──────────────────────────────────────────────────────────────────────────
  4309. 
  4310.    Mouse Driver Configuration
  4311.    MouInitReal         Initializes mouse driver for real mode session
  4312.    MouSetDevStatus     Enables or disables pointer drawing and configures
  4313.                         position reporting
  4314.    MouSetEventMask     Sets mouse driver event mask, controls generation of
  4315.                         mouse event queue records
  4316.    MouSetHotKey        Defines mouse button(s) as system hot key
  4317.    MouSetScaleFact     Sets scaling factors for mouse coordinates
  4318.    MouSynch            Synchronizes access to mouse driver
  4319.  
  4320.    Register/Deregister Mouse Subsystem
  4321.    MouRegister         Registers alternative mouse subsystem
  4322.    MouDeRegister       Deregisters alternative mouse subsystem
  4323.    ──────────────────────────────────────────────────────────────────────────
  4324.  
  4325.  
  4326.    Figure 5-10.  The Mou API at a glance.
  4327.  
  4328.    A process first calls MouOpen to initialize the mouse subsystem for its
  4329.    screen group, if it is not already initialized, and to obtain a logical
  4330.    mouse handle. If the call succeeds, the mouse is "alive" and the
  4331.    application can obtain its state and position; however, the pointer does
  4332.    not appear on the screen until the process calls MouDrawPtr.
  4333.  
  4334.    The process can then use MouReadEventQue to obtain mouse event packets
  4335.    from the mouse driver's FIFO queue. These packets contain flags that
  4336.    indicate the type of event (button press, button release, mouse movement,
  4337.    or a combination), a time stamp, and the mouse screen position.
  4338.    MouReadEventQue has a no-wait option so that the application can avoid
  4339.    blocking if no mouse events are waiting. Unlike other OS/2 kernel
  4340.    functions, the MouReadEventQue wait/no-wait parameter is 1 to wait for an
  4341.    event and 0 to return immediately; MouReadEventQue is also unique in
  4342.    passing the wait/no-wait parameter by reference rather than by value. An
  4343.    application can test the number of waiting events with MouGetNumQueEl.
  4344.  
  4345.    As an alternative to MouReadEventQue, an application can simply poll the
  4346.    mouse position at convenient intervals with MouGetPtrPos. This function,
  4347.    however, does not return the current state of the mouse buttons, and
  4348.    frequent polling can (as with KbdPeek) degrade the performance of the
  4349.    entire system. Regardless of whether it uses MouGetPtrPos or
  4350.    MouReadEventQue, the application should create a separate thread to handle
  4351.    the mouse so that keyboard activity does not affect mouse tracking and
  4352.    vice versa.
  4353.  
  4354.    When the application is finished with the mouse, it can call MouClose to
  4355.    release its mouse handle. Otherwise, the OS/2 kernel closes the mouse
  4356.    handle on behalf of the process as part of the normal cleanup at
  4357.    termination. When the last process using the mouse in the screen group
  4358.    terminates or calls MouClose, the mouse subsystem is disabled for the
  4359.    screen group, and the mouse pointer is removed from the screen.
  4360.  
  4361. ╓┌─┌────────────────────┌────────────────────────────────────────────────────╖
  4362.    Function Number      Operation
  4363.    ──────────────────────────────────────────────────────────────────────────
  4364.    50H                 Enables pointer drawing after session switch
  4365.    51H                 Notifies of display mode change
  4366.    52H                 Notifies of impending session switch
  4367.    53H                 Sets scaling factors
  4368.    54H                 Sets event mask
  4369.    55H                 Sets system hot key button
  4370.    Function Number      Operation
  4371.    ──────────────────────────────────────────────────────────────────────────
  4372.   55H                 Sets system hot key button
  4373.    56H                 Sets pointer shape
  4374.    57H                 Cancels exclusion area (equivalent to MouDrawPtr)
  4375.    58H                 Sets exclusion area (equivalent to MouRemovePtr)
  4376.    59H                 Sets pointer position
  4377.    5AH                 Sets address of protected mode pointer-draw routine
  4378.    5BH                 Sets address of real mode pointer-draw routine
  4379.    5CH                 Sets mouse driver status
  4380.    60H                 Gets number of buttons
  4381.    61H                 Gets number of mickeys per centimeter
  4382.    62H                 Gets mouse driver status
  4383.    63H                 Reads event queue
  4384.    64H                 Gets event queue status
  4385.    65H                 Gets event mask
  4386.    66H                 Gets scaling factors
  4387.    67H                 Gets pointer position
  4388.    68H                 Gets pointer shape
  4389.    69H                 Gets system hot key button
  4390.    ──────────────────────────────────────────────────────────────────────────
  4391.    Function Number      Operation
  4392.    ──────────────────────────────────────────────────────────────────────────
  4393.   ──────────────────────────────────────────────────────────────────────────
  4394.  
  4395.  
  4396.    Figure 5-11.  DosDevIOCtl Category 7 functions for mouse status and
  4397.    control. The majority of these are present to provide the underpinnings
  4398.    for the Mou calls and can be used by applications, but a few are intended
  4399.    only for use by the Task Manager.
  4400.  
  4401.    The default size of the mouse queue is 10 events. If mouse events occur
  4402.    more rapidly than the application can process them, the oldest events are
  4403.    simply overwritten. This will not usually cause a problem because the
  4404.    application is typically interested in the current position of the mouse,
  4405.    not the history of where it has been! The user can increase the size of
  4406.    the mouse event queue with an optional parameter in the DEVICE= statement
  4407.    (in the CONFIG.SYS file) that loads the mouse driver.
  4408.  
  4409.    All processes in a screen group share the same mouse event queue and can
  4410.    read or change the mouse position, event mask, or pointer shape at any
  4411.    time. The Mou functions provide no analog to the KbdGetFocus and
  4412.    KbdFreeFocus calls that would allow a process to prevent interference by
  4413.    other processes in the same group.
  4414.  
  4415.    Mouse support is restricted for programs not running in the Presentation
  4416.    Manager screen group: The MOUSE$ driver works properly in all "standard"
  4417.    text and graphics modes, but the system's mouse cursor can be used only in
  4418.    text modes. If a non─Presentation Manager application selects a graphics
  4419.    mode and intends to use the mouse, it must disable the system's pointer
  4420.    driver with MouSetDevStatus and provide the mouse cursor itself.
  4421.  
  4422.    A small demonstration program, which monitors and displays the mouse's
  4423.    position and status, appears in Figure 5-12.
  4424.  
  4425.    ──────────────────────────────────────────────────────────────────────────
  4426.    /*
  4427.            MOUDEMO.C
  4428.  
  4429.            A simple demo of the OS/2 mouse API.
  4430.  
  4431.            Compile with:  C> cl moudemo.c
  4432.  
  4433.            Usage is:  C> moudemo
  4434.  
  4435.            Copyright (C) 1988 Ray Duncan
  4436.    */
  4437.  
  4438.    #include <stdio.h>
  4439.    #define API unsigned extern far pascal
  4440.  
  4441.    API MouReadEventQue(void far *, unsigned far *, unsigned);
  4442.    API MouOpen(void far *, unsigned far *);
  4443.    API MouClose(unsigned);
  4444.    API MouDrawPtr(unsigned);
  4445.    API VioScrollUp(unsigned, unsigned, unsigned,
  4446.                          unsigned, unsigned, char far *, unsigned);
  4447.    API VioWrtCharStr(void far *, unsigned, unsigned,
  4448.                          unsigned, unsigned);
  4449.    struct _MouEventInfo {  unsigned Flags;
  4450.                            unsigned long Timestamp;
  4451.                            unsigned Row;
  4452.                            unsigned Col;
  4453.                         }  MouEvent ;
  4454.  
  4455.    main(int argc, char *argv[])
  4456.    {
  4457.            char OutStr[40];                /* for output formatting */
  4458.  
  4459.            unsigned Cell = 0x0720;         /* ASCII space and
  4460.                                              normal attribute */
  4461.  
  4462.            unsigned MouHandle;             /* mouse logical handle */
  4463.            unsigned Status;                /* returned from API */
  4464.            int WaitOption = 1;             /* 1 = block for event,
  4465.                                               0 = do not block */
  4466.  
  4467.                                            /* open mouse device */
  4468.            Status = MouOpen(0L, &MouHandle);
  4469.  
  4470.            if(Status)                      /* exit if no mouse */
  4471.            {       printf("\nMouOpen failed.\n");
  4472.                    exit(1);
  4473.            }
  4474.                                            /* clear the screen */
  4475.            VioScrollUp(0, 0, -1, -1, -1, &(char)Cell, 0);
  4476.  
  4477.            puts("Press Both Mouse Buttons To Exit");
  4478.  
  4479.            MouDrawPtr(MouHandle);          /* display mouse cursor */
  4480.  
  4481.            do                              /* format mouse position */
  4482.            {       sprintf(OutStr, "X=%2d Y=%2d", MouEvent.Col, MouEvent.Row);
  4483.  
  4484.                                            /* display mouse position */
  4485.                    VioWrtCharStr(OutStr, strlen(OutStr), 0, 0, 0);
  4486.  
  4487.                                            /* wait for a mouse event */
  4488.                    MouReadEventQue(&MouEvent, &WaitOption, MouHandle);
  4489.  
  4490.                                            /* exit if both buttons down */
  4491.            } while((MouEvent.Flags & 0x14) != 0x14) ;
  4492.  
  4493.            MouClose(MouHandle);            /* release mouse handle */
  4494.  
  4495.            puts("Have a Mice Day!");
  4496.    }
  4497.    ──────────────────────────────────────────────────────────────────────────
  4498.  
  4499.    Figure 5-12.  MOUDEMO.C, a simple demonstration program for the Mou API.
  4500.    This program reads mouse events and displays the mouse position; it
  4501.    terminates when both mouse buttons are pressed simultaneously.
  4502.  
  4503.  
  4504.  Mouse Subsystems and Drivers
  4505.  
  4506.    As with the keyboard, the OS/2 mouse support is divided among several
  4507.    modules (Figure 5-13). The character device driver MOUSE$, loaded from
  4508.    one of the MOUSEXX.SYS files, services mouse interrupts and keeps track of
  4509.    the mouse position and button states. The POINTER$ driver, loaded from
  4510.    POINTDD.SYS, maintains the pointer on the screen. This functional division
  4511.    is not visible at the API level.
  4512.  
  4513.                        ┌─────────────┐
  4514.                        │ Application │───────────────┐
  4515.                        └────────────┘                │
  4516.                             │ Mou API calls           │
  4517.                        ┌────────────┐                │
  4518.                        │MOUCALLS.DLL │                │
  4519.                        └────────────┘                │
  4520.                             │ Private call interface  │ DosDevIOCtl
  4521.                        ┌────────────┐                │
  4522.                        │BMSCALLS.DLL │                │
  4523.                        └────────────┘                │
  4524.                             │ DosDevIOCtl             │
  4525.                        ┌────────────┐                │
  4526.                        │ OS/2 kernel │───────────────┘
  4527.                        └───────────┘
  4528.                            │     │ Request packet interface
  4529.         ┌──────────────────┘     └───────────────────┐
  4530.    ┌──────────────┐   Private call     ┌────────────────┐
  4531.    │ MOUSE$ device │    interface       │ POINTER$ device │
  4532.    │    driver     ├───────────────────│     driver      │
  4533.    └──────────────┘                    └────────────┬────┘
  4534.    ┌────┴──────────┐ Hardware-dependent ┌────────────────┐
  4535.    │     Mouse     │   I/O operations   │      Video      │
  4536.    │   hardware    │                    │     display     │
  4537.    └───────────────┘                    └─────────────────┘
  4538.  
  4539.    Figure 5-13.  Simplified diagram of the relationship between the mouse
  4540.    drivers, dynlink libraries, kernel, and applications.
  4541.  
  4542.    The MOUSE$ driver supports device monitors in much the same manner as the
  4543.    keyboard driver. Each time a mouse "event" occurs, a data packet is passed
  4544.    through the chain of monitor buffers that contains the event type, a time
  4545.    stamp, and the mouse position. A process registered as a mouse monitor can
  4546.    consume, modify, or add data packets to the monitor chain; if it is also
  4547.    registered as a keyboard monitor, it can conceivably translate mouse
  4548.    movements to keystrokes and vice versa! The format of the mouse monitor
  4549.    data packet is shown in Chapter 18.
  4550.  
  4551.    At the dynlink library level, the mouse subsystem is organized much like
  4552.    the keyboard subsystem. The dynlink library BMSCALLS.DLL contains the
  4553.    Basic Mouse Subsystem, which implements the Mou services and communicates
  4554.    with the MOUSE$ driver through DosDevIOCtl calls. MOUCALLS.DLL is the
  4555.    "mouse router"; it contains the entry points for the Mou API functions,
  4556.    and it passes incoming calls to the appropriate subsystem (such as
  4557.    BMSCALLS) on a per-screen-group basis. BMSCALLS and MOUCALLS both run in
  4558.    user mode and thus have no direct access to the hardware. Some or all of
  4559.    the default services provided by BMSCALLS can be replaced using
  4560.    MouRegister and MouDeRegister.
  4561.  
  4562.    A particularly interesting aspect of the mouse subsystem is that the
  4563.    MOUSE$ and POINTER$ drivers communicate directly using a private far call
  4564.    interface. Although this is a wayward arrangement as far as the overall
  4565.    system architecture is concerned, it allows hardware dependence on mouse
  4566.    type and display type to be segregated into two drivers that can be
  4567.    maintained separately while avoiding excessive overhead for pointer
  4568.    tracking.
  4569.  
  4570.    MOUCALLS.DLL establishes the linkage between the mouse and pointer drivers
  4571.    at the time of a MouOpen request. MOUCALLS first invokes DosOpen for
  4572.    POINTER$, and then it calls DosDevIOCtl Category 3 Function 72H to get the
  4573.    entry point. MOUCALLS then performs a DosOpen for MOUSE$, followed by a
  4574.    call to DosDevIOCtl Category 7 Function 5AH to pass it the POINTER$ entry
  4575.    address.
  4576.  
  4577.  
  4578.  
  4579.  ────────────────────────────────────────────────────────────────────────────
  4580.  Chapter 6  The Video Display
  4581.  
  4582.    OS/2 provides application programs with a rich selection of function calls
  4583.    to control the video display. These functions span the complete spectrum
  4584.    of hardware independence:
  4585.  
  4586.    ■  Presentation Manager graphical services
  4587.  
  4588.    ■  DosWrite with the predefined handle for standard output (1) or standard
  4589.       error (2)
  4590.  
  4591.    ■  DosWrite with a handle obtained by opening the logical device CON or
  4592.       SCREEN$
  4593.  
  4594.    ■  The Vio (video subsystem) functions
  4595.  
  4596.    ■  Direct access to the screen group's logical video buffer
  4597.  
  4598.    ■  Direct access to the physical video buffer
  4599.  
  4600.    The Presentation Manager provides applications with a uniform graphical
  4601.    user interface. It supports text fonts, windowing and clipping, pull-down
  4602.    menus, popup dialog boxes, pointing devices, and a broad range of high
  4603.    performance drawing and painting operations. To take full advantage of
  4604.    these capabilities, applications must have a special structure and must
  4605.    abide by an intricate set of conventions. The payoff is complete
  4606.    portability between machines and output devices that support the
  4607.    Presentation Manager, invisible adjustment of the program's output to
  4608.    compensate for the display's resolution and aspect ratio and to exploit
  4609.    available fonts, and a shortened learning curve for the user.
  4610.  
  4611.    When writing character-oriented applications, you can use the general
  4612.    purpose function DosWrite to display text. You can control the video mode,
  4613.    foreground and background colors, and cursor position with ANSI escape
  4614.    sequences embedded in the output. DosWrite is analogous to Int 21H
  4615.    Function 40H (Write to File or Device) under MS-DOS and is most
  4616.    appropriate for filters and other utility programs in which the ability to
  4617.    redirect output is important and interactive performance is not an issue.
  4618.    Programs that use this function avoid the complexity of the Presentation
  4619.    Manager graphical interface but retain device independence and the ability
  4620.    to run in a PM window.
  4621.  
  4622.    You can use the Vio functions, which offer more flexibility and speed than
  4623.    DosWrite, for interactive character-oriented applications that do not
  4624.    require redirectable output. The Vio functions provide such capabilities
  4625.    as scrolling the screen in all four directions, controlling cursor shape,
  4626.    assigning character attributes and colors, and reading strings back from
  4627.    the screen buffer. The Vio calls are a functional superset of the ROM BIOS
  4628.    video driver (Int 10H) calls available under MS-DOS; they are immune to
  4629.    redirection of the standard output and (with the exception of one
  4630.    function) ignore control codes and escape sequences. Applications that use
  4631.    a defined subset of the Vio calls and avoid other hardware dependence will
  4632.    run in a PM window.
  4633.  
  4634.    Last, we come to the two hardware-dependent display methods. You might use
  4635.    them in applications that have special requirements, such as a need to
  4636.    present a graphics display without the aid of the Presentation Manager, or
  4637.    to drive the controller in a mode or resolution not supported by OS/2's
  4638.    built-in screen driver. The first of these techniques is to obtain a
  4639.    selector from OS/2 that gives the application direct access to the screen
  4640.    group's logical video buffer (LVB). The LVB is a "shadow" of the screen
  4641.    group's display; when it is not in the foreground, it receives output from
  4642.    programs in that group. After modifying the contents of the LVB, the
  4643.    program issues an additional command to refresh the physical display
  4644.    buffer──with no effect if the application's screen group is in the
  4645.    background. Use of the LVB allows very high display throughput and does
  4646.    not interfere with processes in other screen groups.
  4647.  
  4648.    The second, and potentially more destructive, hardware-dependent technique
  4649.    is to obtain a selector for the physical video buffer from OS/2. After
  4650.    "locking" the screen with a function call, the application can write
  4651.    directly to the buffer, "unlocking" the screen with another function call
  4652.    when it is finished. If the application contains a segment with IOPL (I/O
  4653.    privilege), it can also issue commands directly to the video adapter's I/O
  4654.    ports. While the screen lock is in effect, the user cannot switch to
  4655.    another screen group, and background tasks cannot "pop up" to interact
  4656.    with the user.
  4657.  
  4658.    This chapter will focus on the DosWrite and Vio calls used by Kernel
  4659.    applications to display text and control the screen. The programming of
  4660.    true Presentation Manager applications is beyond the scope of this book.
  4661.    At the other extreme, the hardware-dependent methods tend to be
  4662.    incompatible with the Presentation Manager and should therefore be
  4663.    avoided.
  4664.  
  4665.  
  4666.  Video Display Modes
  4667.  
  4668.    With one exception──the original Monochrome Display Adapter (MDA)── the
  4669.    video adapters used in OS/2-compatible personal computers support two or
  4670.    more display modes (Figure 6-1). The modes fall into two distinct
  4671.    classes: alphanumeric, or text, modes and all-points-addressable (APA), or
  4672.    graphics, modes. The video adapters have a hybrid interface to the CPU and
  4673.    are controlled by a mixture of memory-mapped and port-addressed I/O.
  4674.  
  4675.    Resolution       Colors      Text/       MDA   CGA   EGA   VGA
  4676.                                 Graphics
  4677.    ──────────────────────────────────────────────────────────────────────────
  4678.    40-by-25         16          Text                             
  4679.    80-by-25         2          Text                             
  4680.    80-by-25         16          Text                             
  4681.    80-by-43         16          Text                              
  4682.    80-by-50         16          Text                               
  4683.    320-by-200       4           Graphics                         
  4684.    320-by-200       16          Graphics                          
  4685.    320-by-200       256         Graphics                           
  4686.    640-by-200       2           Graphics                         
  4687.    640-by-200       16          Graphics                          
  4688.    640-by-350       2          Graphics                          
  4689.    640-by-350       4           Graphics                    
  4690.    640-by-350       16          Graphics                         
  4691.    640-by-480       2           Graphics                           
  4692.    640-by-480       16          Graphics                           
  4693.    ──────────────────────────────────────────────────────────────────────────
  4694.  
  4695.    Figure 6-1.  The video modes available on various video adapters used in
  4696.    OS/2-compatible computers.
  4697.  
  4698.    In text modes, each character on the screen is represented by two adjacent
  4699.    bytes in a buffer, called the refresh (or "regen") buffer, that is
  4700.    dedicated to use by the video controller. The first byte of such a pair
  4701.    contains the character code, and the second byte contains the character
  4702.    "attribute." The attribute controls special display characteristics such
  4703.    as the foreground and background colors, blink, and underline. A hardware
  4704.    character generator translates the ASCII code into a pattern of pixels on
  4705.    the screen at a column and row corresponding to the character's position
  4706.    in the refresh buffer.
  4707.  
  4708.    In graphics modes, the bits in the refresh buffer are mapped directly to
  4709.    the pixels on the screen. This bit mapping might differ drastically
  4710.    between graphics modes with different resolutions. No hardware character
  4711.    generator is available to translate ASCII codes to an array of pixels;
  4712.    instead, the software itself must turn the bits in the buffer on or off to
  4713.    form a character or figure on the display.
  4714.  
  4715.    OS/2's support for graphics modes is, for the most part, exploitable only
  4716.    within a Presentation Manager application. Kernel or Family applications
  4717.    that select a graphics mode cannot run in a PM window and must contain
  4718.    their own software character generators and graphics drawing primitives.
  4719.  
  4720.  
  4721.  Using DosWrite for Output
  4722.  
  4723.    The general purpose function DosWrite accepts a handle for a file or
  4724.    device, the address of the data to be written, the length of the data (up
  4725.    to 65,535 bytes), and the address of a variable. DosWrite returns the
  4726.    actual number of bytes written in the specified variable (Figure 6-2).
  4727.  
  4728.    ──────────────────────────────────────────────────────────────────────────
  4729.    stdout  equ     1               ; standard output handle
  4730.  
  4731.    msg     db      'HELLO'         ; message to be displayed
  4732.    msg_len equ     $-msg           ; length of message
  4733.  
  4734.    wlen    dw      ?               ; receives actual number
  4735.                                    ; of bytes written
  4736.  
  4737.            .
  4738.            .
  4739.            .
  4740.            push    stdout          ; standard output handle
  4741.            push    ds              ; address of text
  4742.            push    offset DGROUP:msg
  4743.            push    msg_len         ; length of text
  4744.            push    ds              ; receives bytes written
  4745.            push    offset DGROUP:wlen
  4746.            call    DosWrite        ; transfer to OS/2
  4747.            or      ax,ax           ; was write successful?
  4748.            jnz     error           ; jump if function failed
  4749.            .
  4750.            .
  4751.            .
  4752.    ──────────────────────────────────────────────────────────────────────────
  4753.  
  4754.    Figure 6-2.  Writing text to the display using DosWrite and the standard
  4755.    output handle.
  4756.  
  4757.    When you use DosWrite to display text on the screen, the handle must be
  4758.    either one of the system's predefined handles for standard output (1) or
  4759.    standard error (2), or a handle obtained from a successful DosOpen of the
  4760.    logical device CON or SCREEN$. The DosOpen method is typically used by
  4761.    processes that need to circumvent any redirection of the standard devices
  4762.    that might be in effect (Figure 6-3).
  4763.  
  4764.    ──────────────────────────────────────────────────────────────────────────
  4765.    sname   db      'CON',0         ; device name
  4766.    shandle dw      ?               ; receives device handle
  4767.    action  dw      ?               ; receives DosOpen action
  4768.    wlen    dw      ?               ; receives bytes written
  4769.  
  4770.    msg     db      'HELLO'         ; message to be displayed
  4771.    msg_len equ     $-msg           ; length of message
  4772.  
  4773.            .
  4774.            .
  4775.            .
  4776.            push    ds              ; address of device name
  4777.            push    offset DGROUP:sname
  4778.            push    ds              ; variable to receive handle
  4779.            push    offset DGROUP:shandle
  4780.            push    ds              ; variable to receive action
  4781.            push    offset DGROUP:action
  4782.            push    0               ; filesize (not applicable)
  4783.            push    0
  4784.            push    0               ; attribute (not applicable)
  4785.            push    1h              ; action = open, no create
  4786.            push    42h             ; mode = rd/wt, deny-none
  4787.            push    0               ; reserved (0)
  4788.            push    0
  4789.            call    DosOpen         ; transfer to OS/2
  4790.            or      ax,ax           ; did open succeed?
  4791.            jnz     error           ; jump if function failed
  4792.            .
  4793.            .
  4794.            .
  4795.            push    shandle         ; handle for CON
  4796.            push    ds              ; address of message
  4797.            push    offset DGROUP:msg
  4798.            push    msg_len         ; length of message
  4799.            push    ds              ; address of variable
  4800.            push    offset DGROUP:wlen
  4801.            call    DosWrite        ; transfer to OS/2
  4802.            or      ax,ax           ; did write succeed?
  4803.            jnz     error           ; jump if function failed
  4804.            .
  4805.            .
  4806.            .
  4807.    ──────────────────────────────────────────────────────────────────────────
  4808.  
  4809.    Figure 6-3.  Bypassing output redirection by opening the screen driver as
  4810.    if it were a file and using the new handle obtained for subsequent text
  4811.    output.
  4812.  
  4813.    DosWrite treats the display as a "glass teletype." It provides line
  4814.    wrapping and screen scrolling; and carriage returns, linefeeds, tabs, bell
  4815.    codes, and backspaces embedded in the output have their expected effects.
  4816.    In general, it always writes the exact number of characters specified. The
  4817.    output terminates prematurely (reflected in the value returned in the
  4818.    variable) only if it is redirected to a disk file and the disk is full, or
  4819.    if the data contains a Ctrl-Z character (1AH).
  4820.  
  4821.    Although DosWrite is appropriate for filters and other programs that
  4822.    process streams of simple text, you should avoid using it in interactive
  4823.    applications. The kernel translates any call to DosWrite that uses a
  4824.    handle for the display to VioWrtTTY anyway, so calling DosWrite is
  4825.    intrinsically less efficient than calling Vio functions directly.
  4826.  
  4827.  Controlling Character Attributes with DosWrite
  4828.  
  4829.    If your program uses DosWrite for display, you can use a subset of the
  4830.    ANSI 3.64─1979 standard escape sequences to select character attributes
  4831.    and colors, clear the screen, and position the cursor. Escape sequences
  4832.    are so called because they always start with the ASCII ESC character code
  4833.    (1BH). You can embed these sequences within other text strings or send
  4834.    them to the screen driver separately with DosWrite. Before you use escape
  4835.    sequences, verify that ANSI support is enabled with the functions
  4836.    VioGetAnsi and VioSetAnsi.
  4837.  
  4838.    Character attributes, such as reverse, high intensity, or underline on
  4839.    monochrome adapters or foreground and background colors on color adapters,
  4840.    are controlled with the escape sequences of the following form:
  4841.  
  4842.      <Esc>[nm
  4843.  
  4844.    where n has the values shown in the following table.
  4845.  
  4846.    Value of n                Attribute
  4847.    ──────────────────────────────────────────────────────────────────────────
  4848.    0                         No special attributes
  4849.    1                         High intensity
  4850.    2                         Low intensity
  4851.    3                         Italic
  4852.    4                         Underline
  4853.    5                         Blink
  4854.    7                         Reverse video
  4855.    8                         Concealed text (no display)
  4856.    ──────────────────────────────────────────────────────────────────────────
  4857.  
  4858.    For color control, you can also replace n with the following values:
  4859.  
  4860.    Foreground               Background              Color
  4861.    ──────────────────────────────────────────────────────────────────────────
  4862.    30                       40                      Black
  4863.    31                       41                      Red
  4864.    32                       42                      Green
  4865.    33                       43                      Yellow
  4866.    34                       44                      Blue
  4867.    35                       45                      Magenta
  4868.    36                       46                      Cyan
  4869.    37                       47                      White
  4870.    ──────────────────────────────────────────────────────────────────────────
  4871.  
  4872.    To specify multiple attributes in the same escape sequence, separate them
  4873.    with semicolons. After you select an attribute, it is applied to all
  4874.    subsequent text written to the screen until another escape sequence alters
  4875.    it. For example, the code illustrated in Figure 6-4 causes all subsequent
  4876.    text to be displayed in reverse video.
  4877.  
  4878.    ──────────────────────────────────────────────────────────────────────────
  4879.    stdout  equ     1               ; standard output handle
  4880.  
  4881.    revstr  db      01bh,'[7m'      ; escape sequence to
  4882.                                    ; select reverse video
  4883.  
  4884.    rev_len equ     $-revstr        ; length of escape sequence
  4885.  
  4886.    wlen    dw      ?               ; receives actual number
  4887.                                    ; of bytes written
  4888.  
  4889.            .
  4890.            .
  4891.            .
  4892.            push    stdout          ; standard output handle
  4893.            push    ds              ; address of escape sequence
  4894.            push    offset DGROUP:revstr
  4895.            push    rev_len         ; length of escape sequence
  4896.            push    ds              ; address of variable
  4897.            push    offset DGROUP:wlen
  4898.            call    DosWrite        ; transfer to OS/2
  4899.            or      ax,ax           ; did write succeed?
  4900.            jnz     error           ; jump if function failed
  4901.            .
  4902.            .
  4903.            .
  4904.    ──────────────────────────────────────────────────────────────────────────
  4905.  
  4906.    Figure 6-4.  Using an escape sequence to select the reverse video
  4907.    attribute for all text that is subsequently displayed.
  4908.  
  4909.  Clearing the Screen and Scrolling with DosWrite
  4910.  
  4911.    Two escape sequences are available to clear all or part of the screen. The
  4912.    first such string clears the screen and positions the cursor in the upper
  4913.    left corner:
  4914.  
  4915.      <Esc>[2J
  4916.  
  4917.    The other string clears from the cursor to the end of the current line and
  4918.    leaves the cursor position unchanged:
  4919.  
  4920.      <Esc>[K
  4921.  
  4922.    You can scroll the screen upward with DosWrite by positioning the cursor
  4923.    on the last line of the screen and then sending a linefeed (0AH)
  4924.    character. The entire new line receives the attribute of the last
  4925.    character on the preceding line. Scrolling in the other three directions
  4926.    is not supported for output with DosWrite.
  4927.  
  4928.  Cursor Control with DosWrite
  4929.  
  4930.    The cursor can be positioned with the sequence
  4931.  
  4932.      <Esc>[row;colH
  4933.  
  4934.    where row is the y coordinate and col is the x coordinate. The row and col
  4935.    values are one-based, decimal numbers expressed as ASCII strings and are
  4936.    not binary values. Position (x,y) = (1,1) is the upper left corner of the
  4937.    screen; the maximum values for the x and y coordinates depend on the
  4938.    current display mode. Escape sequences ending with f have the same effect
  4939.    as those ending with H. An example of cursor positioning is provided in
  4940.    Figure 6-5.
  4941.  
  4942.    ──────────────────────────────────────────────────────────────────────────
  4943.    stdout  equ     1               ; standard output handle
  4944.  
  4945.    movstr  db      01bh,'[25;1H'   ; escape sequence to move
  4946.                                    ; cursor to (x,y) = (1,25)
  4947.  
  4948.    mov_len equ     $-movstr        ; length of escape sequence
  4949.  
  4950.    wlen    dw      ?               ; receives actual number
  4951.                                    ; of bytes written
  4952.  
  4953.            .
  4954.            .
  4955.            .
  4956.            push    stdout          ; standard output handle
  4957.            push    ds              ; address of escape sequence
  4958.            push    offset DGROUP:movstr
  4959.            push    mov_len         ; length of escape sequence
  4960.            push    ds              ; address of variable
  4961.            push    offset DGROUP:wlen
  4962.            call    DosWrite        ; transfer to OS/2
  4963.            or      ax,ax           ; did write succeed?
  4964.            jnz     error           ; jump if function failed
  4965.            .
  4966.            .
  4967.            .
  4968.    ──────────────────────────────────────────────────────────────────────────
  4969.  
  4970.    Figure 6-5.  Using an ANSI escape sequence to position the cursor at
  4971.    screen coordinates (0,24); in other words, the first column of the last
  4972.    line on the screen. Note that coordinates in escape sequences are
  4973.    one-based──expressed relative to a home position of (1,1).
  4974.  
  4975.    Escape sequences also exist to move the cursor relative to its current
  4976.    position; these sequences do not scroll the screen if the cursor reaches
  4977.    the edge:
  4978.  
  4979.      <Esc>[nA──────────Move cursor up n rows
  4980.      <Esc>[nB──────────Move cursor down n rows
  4981.      <Esc>[nC──────────Move cursor right n columns
  4982.      <Esc>[nD──────────Move cursor left n columns
  4983.  
  4984.    To determine the current cursor position, the program can send the
  4985.    following sequence:
  4986.  
  4987.      <Esc>[6n
  4988.  
  4989.    The program can then use DosRead to obtain the cursor position from the
  4990.    standard input in the format
  4991.  
  4992.      <Esc>[row;colR
  4993.  
  4994.    where row and col are one or more ASCII digits. For example, if the cursor
  4995.    is positioned at the one-based coordinates (x,y) = (25,7), the position is
  4996.    reported with the character sequence
  4997.  
  4998.      <Esc>[25;07R
  4999.  
  5000.    Decoding such position reports is inconvenient at best. In most cases, the
  5001.    need for such decoding can be avoided with the two escape sequences
  5002.  
  5003.      <Esc>[s
  5004.      <Esc>[u
  5005.  
  5006.    Note that the driver can "remember" only one position at a time.
  5007.  
  5008.  Mode Control with DosWrite
  5009.  
  5010.    You can use the escape sequence
  5011.  
  5012.      <Esc>[=nh
  5013.  
  5014.    to select the display mode for the active video adapter, where the value
  5015.    of n has the following meanings:
  5016.  
  5017.    Value of n                Video Display Mode
  5018.    ──────────────────────────────────────────────────────────────────────────
  5019.    0                         40-by-25 16-color text (color burst off)
  5020.    1                         40-by-25 16-color text
  5021.    2                         80-by-25 16-color text (color burst off)
  5022.    3                         80-by-25 16-color text
  5023.    4                         320-by-200 4-color graphics
  5024.    5                         320-by-200 4-color graphics (color burst off)
  5025.    6                         640-by-200 2-color graphics
  5026.    ──────────────────────────────────────────────────────────────────────────
  5027.  
  5028.    The values of n are the same as the video display mode numbers used in the
  5029.    old IBM ROM BIOS. You cannot use an escape sequence to select the higher
  5030.    resolution graphics modes available on an EGA or VGA adapter, to obtain
  5031.    the current display mode, or to activate another adapter if the system
  5032.    contains more than one.
  5033.  
  5034.  
  5035.  Using Vio Functions for Output
  5036.  
  5037.    The Vio subsystem is a high performance, character-oriented display
  5038.    interface for Kernel and Family applications. The Vio functions are
  5039.    summarized by category in Figure 6-6.
  5040.  
  5041.    You can use several different Vio functions to display text. The simplest
  5042.    and most general is VioWrtTTY, which is called with the address and length
  5043.    of a string and a Vio handle. The function returns a status code of zero
  5044.    if the write succeeded or an error code if it failed. Figure 6-7 on page
  5045.    105 demonstrates a call to the function.
  5046.  
  5047. ╓┌─┌─────────────────────────┌───────────────────────────────────────────────╖
  5048.    Function                  Description
  5049.    ──────────────────────────────────────────────────────────────────────────
  5050.    Display
  5051.    VioWrtTTY               Writes string in "teletype" mode
  5052.    VioWrtCellStr           Writes string of alternating characters and
  5053.                              attribute bytes
  5054.    VioWrtCharStr           Writes string without attribute control
  5055.    VioWrtCharStrAtt        Writes string applying the same attribute to
  5056.                              each character
  5057.    VioReadCellStr          Reads characters and attributes from display
  5058.    VioReadCharStr          Reads characters from display
  5059.  
  5060.    Replication
  5061.    VioWrtNAttr             Replicates attribute byte
  5062.    VioWrtNCell             Replicates character and attribute byte
  5063.    Function                  Description
  5064.    ──────────────────────────────────────────────────────────────────────────
  5065.   VioWrtNCell             Replicates character and attribute byte
  5066.    VioWrtNChar             Replicates character
  5067.  
  5068.    Cursor Size and Position
  5069.    VioGetCurPos            Gets cursor position
  5070.    VioSetCurPos            Sets cursor position
  5071.    VioGetCurType           Gets cursor shape and size
  5072.    VioSetCurType           Sets cursor shape and size
  5073.  
  5074.    Scroll or Clear Screen
  5075.    VioScrollDn             Scrolls display down
  5076.    VioScrollLf             Scrolls display left
  5077.    VioScrollRt             Scrolls display right
  5078.    VioScrollUp             Scrolls display up
  5079.  
  5080.    Display Mode Control
  5081.    VioSetAnsi              Enables or disables ANSI support
  5082.    VioSetCp                Selects code page
  5083.    VioSetFont              Downloads display font
  5084.    Function                  Description
  5085.    ──────────────────────────────────────────────────────────────────────────
  5086.   VioSetFont              Downloads display font
  5087.    VioSetMode              Selects display mode
  5088.    VioSetState             Sets palette, border color, or blink/intensity
  5089.                              toggle
  5090.  
  5091.    Mode Information
  5092.    VioGetAnsi              Gets ANSI support state
  5093.    VioGetBuf               Gets selector for logical video buffer
  5094.    VioGetConfig             Gets video adapter information
  5095.    VioGetCp                Gets current code page
  5096.    VioGetFont              Gets character dimensions and font
  5097.    VioGetMode               Gets current display mode
  5098.    VioGetPhysBuf           Gets selector for physical video buffer
  5099.    VioGetState             Gets palette, border color, and blink/intensity
  5100.                              toggle
  5101.  
  5102.    Miscellaneous Functions
  5103.    VioPrtSc                Prints screen
  5104.    VioPrtScToggle          Turns print echo on or off
  5105.    Function                  Description
  5106.    ──────────────────────────────────────────────────────────────────────────
  5107.   VioPrtScToggle          Turns print echo on or off
  5108.    VioScrLock              Disables screen switching
  5109.    VioScrUnLock            Enables screen switching
  5110.    VioShowBuf              Updates physical display buffer from logical
  5111.                              buffer
  5112.  
  5113.    Pop-Up Support
  5114.    VioPopUp                 Allocates full-screen text-mode popup display
  5115.    VioEndPopUp              Deallocates popup display
  5116.  
  5117.    Support for Non-PM Graphics Applications
  5118.    VioSavRedrawWait        Returns when screen should be saved or redrawn
  5119.    VioSavRedrawUndo        Cancels VioSavRedrawWait call by another thread
  5120.    VioModeWait             Returns when display mode should be restored
  5121.    VioModeUndo             Cancels VioModeWait call by another thread
  5122.  
  5123.    Advanced Vio Functions
  5124.    VioAssociate             Associates Vio presentation space with device
  5125.                              context
  5126.    Function                  Description
  5127.    ──────────────────────────────────────────────────────────────────────────
  5128.                             context
  5129.    VioCreateLogFont         Creates logical font for use with Vio
  5130.                              presentation space
  5131.    VioCreatePS              Allocates Vio presentation space
  5132.    VioDeleteSetId           Releases logical font identifier
  5133.    VioDestroyPS             Destroys Vio presentation space
  5134.    VioGetDeviceCellSize     Returns character cell height and width
  5135.    VioGetOrg                Returns screen coordinates for origin of Vio
  5136.                              presentation space
  5137.    VioQueryFonts            Returns list of available fonts for specified
  5138.                              typeface
  5139.    VioQuerySetIds           Returns list of loaded fonts
  5140.    VioSetDeviceCellSize     Sets character cell height and width
  5141.    VioSetOrg                Sets screen coordinates of origin for Vio
  5142.                              presentation space
  5143.    VioShowPS                Updates display from Vio presentation space
  5144.  
  5145.    Vio Function Replacement
  5146.    VioRegister             Replaces default Vio functions with new routines
  5147.    Function                  Description
  5148.    ──────────────────────────────────────────────────────────────────────────
  5149.   VioRegister             Replaces default Vio functions with new routines
  5150.    VioDeRegister           Deregisters alternative Vio routines
  5151.    ──────────────────────────────────────────────────────────────────────────
  5152.  
  5153.  
  5154.    Figure 6-6.  Vio functions at a glance. The advanced Vio functions are
  5155.    present to support the OS/2 Presentation Manager and are not available in
  5156.    OS/2 version 1.0.
  5157.  
  5158.    ──────────────────────────────────────────────────────────────────────────
  5159.    msg     db      'Hello World'   ; message to display
  5160.    msg_len equ $-msg               ; length of message
  5161.  
  5162.            .
  5163.            .
  5164.            .
  5165.            push    ds              ; address of message
  5166.            push    offset DGROUP:msg
  5167.            push    msg_len         ; length of message
  5168.            push    0               ; Vio handle
  5169.            call    VioWrtTTY       ; transfer to OS/2
  5170.            or      ax,ax           ; did write succeed?
  5171.            jnz     error           ; jump if function failed
  5172.            .
  5173.            .
  5174.            .
  5175.    ──────────────────────────────────────────────────────────────────────────
  5176.  
  5177.    Figure 6-7.  Using VioWrtTTY to write the string Hello World to the screen
  5178.    at the current cursor position. The cursor position is updated
  5179.    appropriately after the write.
  5180.  
  5181.    VioWrtTTY handles the control characters linefeed, carriage return,
  5182.    backspace, and bell properly. Line wrapping and scrolling are provided
  5183.    (unless they are intentionally disabled), the cursor position is updated,
  5184.    and ANSI escape sequences described for DosWrite are interpreted unless
  5185.    ANSI support is turned off. The attribute for all characters is the one
  5186.    most recently selected with the appropriate ANSI escape sequence; when the
  5187.    screen is scrolled, the entire new line is given the attribute of the last
  5188.    character written to the previous line.
  5189.  
  5190.    Ordinarily, the only error code that VioWrtTTY returns is 436 (Invalid Vio
  5191.    Handle). Providing the function with an invalid string address or length
  5192.    does not usually result in an error code but may well cause a GP fault
  5193.    that terminates the process.
  5194.  
  5195.    Three other Vio calls, which are slightly trickier to use, are also
  5196.    available for displaying text: VioWrtCharStr, VioWrtCharStrAtt, and
  5197.    VioWrtCellStr. These functions are faster than VioWrtTTY and provide
  5198.    direct control over screen placement, but they do not update the cursor
  5199.    position. Their support for line wrapping is limited: If the text is too
  5200.    long for the current line, it wraps onto the next line; however, if the
  5201.    end of the screen is reached, any remaining characters are discarded, the
  5202.    screen is not scrolled, and no error is returned. All three functions
  5203.    ignore ANSI escape sequences or control characters; any nonalphanumeric
  5204.    characters embedded in the string are displayed as their graphics
  5205.    character equivalents.
  5206.  
  5207.    VioWrtCharStr is the simplest of the three functions listed above. It
  5208.    accepts the address and length of a string, the screen position at which
  5209.    to begin writing the string, and a Vio handle. The new text assumes the
  5210.    attributes of the characters previously displayed at the same screen
  5211.    positions.
  5212.  
  5213.    The VioWrtCharStrAtt call is similar to the VioWrtCharStr function; it
  5214.    has, however, one additional parameter: the address of an attribute byte
  5215.    which is applied to every character in the string (as demonstrated in
  5216.    Figure 6-8). On adapters with monochrome monitors, the attribute byte
  5217.    specifies normal or reverse video, intensity (highlight), blinking, and
  5218.    underline (Figure 6-9). On adapters with color monitors in text modes,
  5219.    the attribute byte contains the background color in the upper four bits
  5220.    and the foreground color in the lower four bits (Figure 6-10).
  5221.  
  5222.    ──────────────────────────────────────────────────────────────────────────
  5223.    attr    db      70h             ; reverse video attribute
  5224.  
  5225.    msg     db      'Hello World'   ; message to display
  5226.    msg_len equ     $-msg           ; length of message
  5227.  
  5228.            .
  5229.            .
  5230.            .
  5231.            push    ds              ; address of string
  5232.            push    offset DGROUP:msg
  5233.            push    msg_len         ; length of string
  5234.            push    5               ; y position
  5235.            push    10              ; x position
  5236.            push    ds              ; video attribute address
  5237.            push    offset DGROUP:attr
  5238.            push    0               ; Vio handle
  5239.            call    VioWrtCharStrAtt; transfer to OS/2
  5240.            or      ax,ax           ; did write succeed?
  5241.            jnz     error           ; jump if function failed
  5242.            .
  5243.            .
  5244.            .
  5245.    ──────────────────────────────────────────────────────────────────────────
  5246.  
  5247.    Figure 6-8.  Using the VioWrtCharStrAtt function to write to the screen.
  5248.    This code displays the string Hello World in reverse video at cursor
  5249.    location (10,5) (column 10, row 5).
  5250.  
  5251.    VioWrtCellStr displays a string that consists of alternating character and
  5252.    attribute bytes. VioWrtCellStr is designed to be used in combination with
  5253.    VioReadCellStr to restore an area of the display that was previously saved
  5254.    into a buffer. This function is not appropriate for routine text output
  5255.    for a number of reasons. Generating initialized strings with embedded
  5256.    attribute bytes is awkward in most languages, and such strings are bulky.
  5257.    Furthermore, applications rarely need to display a string where each
  5258.    successive character has a different attribute.
  5259.  
  5260.      7     6     5     4     3     2     1     0
  5261.    ┌───┬───────────────────┬───┬───────────────────┐
  5262.    │ B │    Background     │ I │    Foreground     │
  5263.    └─┬─┴───────────────────┴─┬─┴───────────────────┘
  5264.      └──Blink (default) or   └──Foreground intensity (default)
  5265.         background intensity    or character select
  5266.  
  5267.    Display                  Background              Foreground
  5268.    ──────────────────────────────────────────────────────────────────────────
  5269.    No display (black)       000                     000
  5270.    No display (white)      111                     111
  5271.    Underline                000                     001
  5272.    Normal video             000                     111
  5273.    Reverse video            111                     000
  5274.    ──────────────────────────────────────────────────────────────────────────
  5275.  
  5276.    Figure 6-9.  The mapping of attribute bytes for the monochrome display
  5277.    adapter (MDA) or for the EGA or VGA in monochrome text mode.
  5278.  
  5279.      7     6     5     4     3     2     1     0
  5280.    ┌───┬───────────────────┬───┬───────────────────┐
  5281.    │ B │    Background     │ I │    Foreground     │
  5282.    └─┬─┴───────────────────┴─┬─┴───────────────────┘
  5283.      └──Blink                └──Intensity
  5284.  
  5285.    Value  Color         Value Color
  5286.    ──────────────────────────────────────────────────────────────────────────
  5287.     0     Black          8    Gray
  5288.     1     Blue           9    Light blue
  5289.     2     Green         10    Light green
  5290.     3     Cyan          11    Light cyan
  5291.     4     Red           12    Light red
  5292.     5     Magenta       13    Light magenta
  5293.     6     Brown         14    Yellow
  5294.     7     White         15    Intense white
  5295.    ──────────────────────────────────────────────────────────────────────────
  5296.  
  5297.    Figure 6-10.  The attribute byte for CGA, EGA, or VGA graphics adapters in
  5298.    color text modes is divided into two 4-bit fields that control the
  5299.    foreground and background colors. The color assignments shown are fixed
  5300.    for the CGA and are the defaults for the EGA or VGA. (Other color
  5301.    assignments can be obtained by programming the palette.) These assignments
  5302.    also assume that the B or I bit controls intensity.
  5303.  
  5304.    The Vio functions VioWrtNChar, VioWrtNAttr, and VioWrtNCell offer some
  5305.    special capabilities that supplement the other text display functions, and
  5306.    they require similar parameters. You can use VioWrtNAttr to modify the
  5307.    attribute bytes of a selected area of the screen without disturbing the
  5308.    text displayed at that position; VioWrtNChar and VioWrtNCell let you draw
  5309.    borders and highlighted areas efficiently in text mode.
  5310.  
  5311.    The Vio functions are not affected by command line redirection parameters.
  5312.    An OS/2 application that wants to take full advantage of the Vio interface
  5313.    while allowing redirection can call DosQHandType with the standard output
  5314.    handle during its initialization to determine whether standard output has
  5315.    been redirected. If DosQHandType indicates that standard output is
  5316.    associated with a file or a pipe, the application should use DosWrite
  5317.    throughout its execution. If the standard output is a character device,
  5318.    and the device attribute word returned by DosQHandType has bit 1 set, the
  5319.    application can proceed to use the Vio calls.
  5320.  
  5321.  Scrolling and Clearing the Screen with Vio
  5322.  
  5323.    The Vio calls allow great flexibility in scrolling or clearing selected
  5324.    areas of the screen. You can use the scroll functions VioScrollUp,
  5325.    VioScrollDn, VioScrollLf, and VioScrollRt to clear a window of arbitrary
  5326.    size and position or to scroll it up, down, left, or right any number of
  5327.    columns or rows. You can specify any character-attribute pair to fill the
  5328.    newly blanked or scrolled lines. All four functions have the following
  5329.    parameters: the screen coordinates of the upper left corner and lower
  5330.    right corner of a window, the number of lines to be scrolled or blanked,
  5331.    the address of a character and attribute pair to fill the blanked lines,
  5332.    and a Vio handle.
  5333.  
  5334.    You can clear the entire display in any mode without first determining the
  5335.    screen dimensions with a special-case call to any of the four scroll
  5336.    functions: Use an upper left coordinate of (0,0), a lower right coordinate
  5337.    of (-1,-1), and the value -1 as the number of lines to scroll. Figure
  5338.    6-11 demonstrates clearing the screen to ASCII blanks with a normal video
  5339.    attribute. Scrolling a selected portion of the screen is also easily done,
  5340.    as demonstrated in Figure 6-12.
  5341.  
  5342.    ──────────────────────────────────────────────────────────────────────────
  5343.    cell    db      20h,07h         ; ASCII blank, normal video
  5344.            .
  5345.            .
  5346.            .
  5347.            push    0               ; y upper left
  5348.            push    0               ; x upper left
  5349.            push    -1              ; y lower right
  5350.            push    -1              ; x lower right
  5351.            push    -1              ; number of blank lines
  5352.            push    ds              ; char/attrib address
  5353.            push    offset DGROUP:cell
  5354.            push    0               ; Vio handle
  5355.            call    VioScrollUp     ; transfer to OS/2
  5356.            or      ax,ax           ; did function succeed?
  5357.            jnz     error           ; jump if function failed
  5358.            .
  5359.            .
  5360.            .
  5361.    ──────────────────────────────────────────────────────────────────────────
  5362.  
  5363.    Figure 6-11.  Clearing the screen to ASCII blanks with a normal video
  5364.    attribute, using one of the four Vio scroll functions. This special-case
  5365.    call with an upper left window coordinate of (0,0), lower right coordinate
  5366.    of (-1,-1), and -1 as the number of lines to scroll does not require you
  5367.    to first determine the dimensions of the active display.
  5368.  
  5369.    ──────────────────────────────────────────────────────────────────────────
  5370.    cell    db      20h,07h         ; ASCII blank, normal video
  5371.            .
  5372.            .
  5373.            .
  5374.            push    12              ; y upper left
  5375.            push    0               ; x upper left
  5376.            push    24              ; y lower right
  5377.            push    79              ; x lower right
  5378.            push    1               ; number of lines to scroll
  5379.            push    ds              ; address of char & attrib
  5380.            push    offset DGROUP:cell
  5381.            push    0               ; Vio handle
  5382.            call    VioScrollUp     ; transfer to OS/2
  5383.            or      ax,ax           ; did scroll succeed?
  5384.            jnz     error           ; jump if function failed
  5385.            .
  5386.            .
  5387.            .
  5388.    ──────────────────────────────────────────────────────────────────────────
  5389.  
  5390.    Figure 6-12.  Scrolling selected portions of the screen in any direction
  5391.    can be accomplished very easily with the VioScrollxx functions. For
  5392.    example, this code scrolls the bottom half of the screen up one line,
  5393.    filling the newly blanked line with ASCII blanks with a normal video
  5394.    attribute, leaving the top half of the screen untouched.
  5395.  
  5396.  Cursor Control with Vio
  5397.  
  5398.    The Vio subsystem supports a comprehensive set of functions to control
  5399.    cursor shape and position. The efficiency of these functions relieves you
  5400.    of the need to access the video hardware directly.
  5401.  
  5402.    Use the function VioSetCurPos to position the cursor. In addition to being
  5403.    faster than the ANSI driver method, VioSetCurPos is also much simpler to
  5404.    use. The zero-based binary screen coordinates are simply pushed onto the
  5405.    stack, instead of being converted to one-based ASCII strings and embedded
  5406.    in an ANSI escape sequence. For example, the code in Figure 6-13
  5407.    positions the cursor on the first column of the last line of the screen.
  5408.  
  5409.    Use the parallel function VioGetCurPos to obtain the current cursor
  5410.    location (Figure 6-14). Both VioSetCurPos and VioGetCurPos use zero-based
  5411.    text coordinates and assume that the coordinates of the home position──the
  5412.    upper left corner of the screen──are (0,0).
  5413.  
  5414.    The functions VioGetCurType and VioSetCurType obtain or change the cursor
  5415.    height, width, and hidden/visible attribute. Both use a 4-word data
  5416.    structure with the same format──quite convenient when the cursor must be
  5417.    altered or hidden temporarily (Figure 6-15). You can use VioGetMode to
  5418.    determine the vertical resolution and number of text lines in the current
  5419.    screen mode, from which you can calculate the size of the character cell
  5420.    and the valid starting and ending scan lines for the cursor.
  5421.  
  5422.    ──────────────────────────────────────────────────────────────────────────
  5423.            .
  5424.            .
  5425.            .
  5426.            push    24              ; y coordinate
  5427.            push    0               ; x coordinate
  5428.            push    0               ; Vio handle
  5429.            call    VioSetCurPos    ; transfer to OS/2
  5430.            or      ax,ax           ; did function succeed?
  5431.            jnz     error           ; jump if function failed
  5432.            .
  5433.            .
  5434.            .
  5435.    ──────────────────────────────────────────────────────────────────────────
  5436.  
  5437.    Figure 6-13.  Cursor positioning with the Vio calls. This code places the
  5438.    cursor at position (x,y) = (0,24), that is, on the first column of the
  5439.    twenty-fifth row of the screen. Compare this fragment with Figure 6-5.
  5440.  
  5441.    ──────────────────────────────────────────────────────────────────────────
  5442.    CurY    dw      ?               ; cursor y position
  5443.    CurX    dw      ?               ; cursor x position
  5444.  
  5445.  
  5446.            .
  5447.            .
  5448.            .
  5449.            push    ds              ; receives y coord
  5450.            push    offset DGROUP:CurY
  5451.            push    ds              ; receives x coord
  5452.            push    offset DGROUP:CurX
  5453.            push    0               ; Vio handle
  5454.            call    VioGetCurPos    ; transfer to OS/2
  5455.            or      ax,ax           ; cursor position obtained?
  5456.            jnz     error           ; jump if function failed
  5457.            .
  5458.            .
  5459.            .
  5460.    ──────────────────────────────────────────────────────────────────────────
  5461.  
  5462.    Figure 6-14.  Reading the cursor position with VioGetCurPos. The cursor
  5463.    location is returned in text coordinates that assume a home position of
  5464.    (0,0).
  5465.  
  5466.    ──────────────────────────────────────────────────────────────────────────
  5467.    CurData label   word            ; cursor data structure
  5468.    CurBeg  dw      ?               ; starting scan line
  5469.    CurEnd  dw      ?               ; ending scan line
  5470.    CurWid  dw      ?               ; width (0 = default)
  5471.    CurAttr dw      ?               ; cursor attribute
  5472.                                    ; (0 = visible, -1 = hidden)
  5473.  
  5474.    CurPrev dw      ?               ; previous cursor start line
  5475.  
  5476.            .
  5477.            .
  5478.            .
  5479.            push    ds              ; receives cursor info
  5480.            push    offset DGROUP:CurData
  5481.            push    0               ; Vio handle
  5482.            call    VioGetCurType   ; transfer to OS/2
  5483.            or      ax,ax           ; cursor info obtained?
  5484.            jnz     error           ; jump if function failed
  5485.            mov     ax,CurBeg       ; save cursor starting line
  5486.            mov     CurPrev,ax
  5487.            mov     CurBeg,0        ; force starting line to
  5488.                                    ; zero to get block cursor
  5489.  
  5490.            push    ds              ; contains cursor info
  5491.            push    offset DGROUP:CurData
  5492.            push    0               ; Vio handle
  5493.            call    VioSetCurType   ; transfer to OS/2
  5494.            or      ax,ax           ; did we change cursor?
  5495.            jnz     error           ; jump if function failed
  5496.  
  5497.            .
  5498.            .                       ; other code goes here...
  5499.            .
  5500.  
  5501.            mov     ax,CurPrev      ; restore cursor shape
  5502.            mov     CurBeg,ax
  5503.  
  5504.            push    ds              ; contains cursor info
  5505.            push    offset DGROUP:CurData
  5506.            push    0               ; Vio handle
  5507.            call    VioSetCurType   ; transfer to OS/2
  5508.            or      ax,ax           ; did we change cursor?
  5509.            jnz     error           ; jump if function failed
  5510.            .
  5511.            .
  5512.            .
  5513.    ──────────────────────────────────────────────────────────────────────────
  5514.  
  5515.    Figure 6-15.  Example of altering the cursor to a block and later
  5516.    restoring it to its original shape.
  5517.  
  5518.  Mode Control with Vio
  5519.  
  5520.    The functions VioGetMode and VioSetMode query or select the video mode
  5521.    without resorting to the "magic numbers" inherited from the original IBM
  5522.    PC ROM BIOS. Both functions use a data structure that specifies the
  5523.    adapter type, text or graphics mode, color burst enable, and the number of
  5524.    displayable colors, text columns and rows, and pixel columns and rows.
  5525.  
  5526.    This approach is open-ended. It allows the application to deal with the
  5527.    adapter on the basis of its capabilities and doesn't require the
  5528.    programmer to remember an ever-expanding list of "mode numbers" that have
  5529.    less and less relationship to the display modes they represent. For
  5530.    example, the code in Figure 6-16 selects 80-by-25 16-color text mode with
  5531.    color burst enabled and is exactly equivalent to the escape sequence
  5532.  
  5533.      <Esc>[3h
  5534.  
  5535.    used with DosWrite or VioWrtTTY.
  5536.  
  5537.    ──────────────────────────────────────────────────────────────────────────
  5538.                                    ; video mode data structure
  5539.    ModeInf dw      8               ; length of structure
  5540.            db      1               ; non-MDA, text mode, color
  5541.            db      4               ; 16 colors
  5542.            dw      80              ; 80 text columns
  5543.            dw      25              ; 25 text rows
  5544.  
  5545.            .
  5546.            .
  5547.            .
  5548.            push    ds              ; contains mode info
  5549.            push    offset DGROUP:ModeInf
  5550.            push    0               ; Vio handle
  5551.            call    VioSetMode      ; transfer to OS/2
  5552.            or      ax,ax           ; did function succeed?
  5553.            jnz     error           ; jump if function failed
  5554.            .
  5555.            .
  5556.            .
  5557.    ──────────────────────────────────────────────────────────────────────────
  5558.  
  5559.    Figure 6-16.  The functions VioGetMode and VioSetMode interrogate or
  5560.    select the display mode based on the controller's capabilities rather than
  5561.    magic numbers. This code selects 80-by-25 16-color text mode with color
  5562.    burst enabled.
  5563.  
  5564.  Pop-Up Support
  5565.  
  5566.    The functions VioPopUp and VioEndPopUp let a background process
  5567.    temporarily assume control of the screen and interact with the user.
  5568.    Background processes, which are launched with the DETACH command, cannot
  5569.    ordinarily perform Kbd or Mou input calls, and their output to the screen
  5570.    is discarded. A process can determine whether it was started with DETACH
  5571.    by issuing a VioGetAnsi function call──this call returns an error code if
  5572.    output calls will also fail.
  5573.  
  5574.    To gain access to the screen, the background process first calls VioPopUp.
  5575.    This function provides two options: wait or no-wait, and transparent or
  5576.    nontransparent. If wait is specified, the calling process is suspended
  5577.    until the screen is available; if no-wait is selected, the function
  5578.    returns immediately with an error code if the screen cannot be preempted
  5579.    (for example, if another background process has called VioPopUp).
  5580.  
  5581.    If the VioPopUp call is successful, the transparent/nontransparent option
  5582.    comes into play. First, the current contents of the screen (belonging to
  5583.    the foreground process) are saved by the system. If the nontransparent
  5584.    option was selected, the screen is blanked and placed into an 80-by-25
  5585.    text mode, and the cursor is placed in the home position (x,y = 0,0).
  5586.    Otherwise, no mode change occurs and the screen contents are left intact.
  5587.    In either case, all subsequent Vio calls by the background process are
  5588.    directed to the active display.
  5589.  
  5590.    At this point, the background process can interact freely with the user.
  5591.    Other processes continue to run normally, their output going to the usual
  5592.    logical video buffer until they require input or call VioPopUp, at which
  5593.    point they are blocked. When the background process is finished with its
  5594.    pop-up, it must call VioEndPopUp to release control of the screen. The
  5595.    state of the display at the time of the pop-up is then restored,
  5596.    regardless of whether the transparent or nontransparent option was used.
  5597.    Figure 6-17 contains the skeleton code for a screen pop-up.
  5598.  
  5599.    ──────────────────────────────────────────────────────────────────────────
  5600.    msg     db      'Surprise!'     ; message for popup screen
  5601.    msg_len equ     $-msg           ; length of message
  5602.  
  5603.    option  dw      1               ; Option flags for VioPopUp
  5604.                                    ; bit 0: 1 = wait, 0 = no-wait
  5605.                                    ; bit 1: 1 = transparent mode
  5606.                                    ;        0 = nontransparent mode
  5607.  
  5608.    kbddata db      10 dup (0)      ; structure for KbdCharIn
  5609.  
  5610.            .
  5611.            .
  5612.            .
  5613.                                    ; put up popup window...
  5614.            push    ds              ; address of option flags
  5615.            push    offset DGROUP:option
  5616.            push    0               ; Vio handle
  5617.            call    VioPopUp        ; transfer to OS/2
  5618.            or      ax,ax           ; did we capture display?
  5619.            jnz     error           ; jump if function failed
  5620.                                    ; display popup message...
  5621.            push    ds              ; address of message
  5622.            push    offset DGROUP:msg
  5623.            push    msg_len         ; length of message
  5624.            push    12              ; y
  5625.            push    (80-msg_len)/2  ; x (center it)
  5626.            push    0               ; Vio handle
  5627.            call    VioWrtCharStr   ; transfer to OS/2
  5628.  
  5629.                                    ; wait for any key...
  5630.            push    ds              ; receives keyboard data
  5631.            push    offset DGROUP:kbddata
  5632.            push    0               ; 0 = wait for a character
  5633.            push    0               ; default keyboard handle
  5634.            call    KbdCharIn       ; transfer to OS/2
  5635.  
  5636.                                    ; take down popup window...
  5637.            push    0               ; Vio handle
  5638.            call    VioEndPopUp     ; transfer to OS/2
  5639.            .                       ; (should never fail)
  5640.            .
  5641.            .
  5642.    ──────────────────────────────────────────────────────────────────────────
  5643.  
  5644.    Figure 6-17.  A background process using VioPopUp and VioEndPopUp to
  5645.    interact with the user. Note that after a successful call to VioPopUp, the
  5646.    program must be careful not to take any branch away from the main line of
  5647.    execution toward VioEndPopUp.
  5648.  
  5649.    A background process that uses VioPopUp may have difficulty getting access
  5650.    to the screen if the current foreground process is computation-intensive
  5651.    rather than I/O-intensive. This is because the threads of a foreground
  5652.    process get an artificial boost to their priority to keep the user happy,
  5653.    and OS/2's scheduler always chooses the thread with the highest priority
  5654.    that is ready to run. The background process can circumvent this problem
  5655.    by promoting the VioPopUp thread to a time-critical priority then ensuring
  5656.    that this thread is blocking at all other times.
  5657.  
  5658.    Use of VioPopUp should be kept to a minimum and reserved for true
  5659.    background processes. This is because screen group switching is disabled
  5660.    during a VioPopUp...VioEndPopUp sequence, and the abrupt transition from a
  5661.    normal display to the largely blank display of a nontransparent pop-up
  5662.    (with a possible concomitant change in display mode) can be startling and
  5663.    disruptive to the user. An ordinary application that uses popup windows as
  5664.    part of its normal interaction can achieve much more pleasing results by
  5665.    using the VioReadCellStr and VioWrtCellStr functions to save and restore
  5666.    small portions of the display.
  5667.  
  5668.  
  5669.  Video Subsystems and Drivers
  5670.  
  5671.    The OS/2 substructure for the video support available to an application is
  5672.    organized in an atypical fashion (Figure 6-18). The character device
  5673.    driver SCREEN$, loaded from the file SCREEN01.SYS or SCREEN02.SYS, plays
  5674.    little role in the control of the display. Its main function is to return
  5675.    a selector for the physical video buffer; it has no documented DosDevIOCtl
  5676.    calls.
  5677.  
  5678.                         ┌────────────────────┐
  5679.                         │     Kernel or      │
  5680.                         │ Family application │
  5681.                         └──────────────────┘
  5682.          ┌───────────────────┘          └───────────────────┐
  5683.          │ Vio calls                                        │ DosWrite
  5684.    ┌─────────────┐           VioWrtTTY           ┌─────────────┐
  5685.    │ VIOCALLS.DLL │──────────────────────────────┤ DOSCALL1.DLL │
  5686.    └─────────────┘─────────────────────┐        └─────────┬────┘
  5687.          │ Private call                  │                  │
  5688.          │ interface                   ┌─────────────┐     │
  5689.    ┌─────────────┐                    │ ANSICALL.DLL │     │ DosDevIOCtl
  5690.    │ BVSCALLS.DLL │───────────────────└──────────────┘     │
  5691.    ├──────────────┤                                         │
  5692.    │ IOPL segment │                               ┌─────────────┐
  5693.    └─────────────┘                               │  OS/2 kernel │
  5694.          │                                        └─────────────┘
  5695.          │                                                  │ Request packet
  5696.          │                                                  │ interface
  5697.          │                                        ┌─────────────┐
  5698.          │                                        │   SCREEN$    │
  5699.          │                                        │device driver │
  5700.          │                ┌───────────────┐       └─────────────┘
  5701.          └───────────────│ Video adapter │────────────────┘
  5702.                           └───────────────┘
  5703.  
  5704.    Figure 6-18.  The system modules responsible for video support.
  5705.  
  5706.    The dynlink library BVSCALLS.DLL is the Basic Video Subsystem that
  5707.    implements the Vio services. BVSCALLS contains the true driver for the
  5708.    video adapter; it manipulates the physical video buffer (using the
  5709.    selector obtained from SCREEN$). BVSCALLS contains an IOPL segment so that
  5710.    it can read and write the adapter's I/O ports to set the display mode,
  5711.    program the palette, and so forth.
  5712.  
  5713.    The dynlink library VIOCALLS.DLL is a "router." It contains the entry
  5714.    points for the Vio API and passes the calls onward to the appropriate
  5715.    subsystem (such as BVSCALLS) on a per-screen-group basis. Interpretation
  5716.    of ANSI escape sequences (when ANSI support is enabled) is carried out by
  5717.    ANSICALL.DLL.
  5718.  
  5719.    Although BVSCALLS is the default video subsystem for each screen group,
  5720.    part or all of its services can be replaced on a per-screen-group basis.
  5721.    To activate a new subsystem, call VioRegister with a module (dynlink
  5722.    library) name, an entry point name, and a set of flags that indicate which
  5723.    Vio services the module supports; those functions not present continue to
  5724.    be passed to BVSCALLS. VioDeRegister deactivates an alternative Vio
  5725.    subsystem and causes all Vio processing to rely on BVSCALLS.
  5726.  
  5727.  
  5728.  
  5729.  ────────────────────────────────────────────────────────────────────────────
  5730.  Chapter 7  Printer and Serial Ports
  5731.  
  5732.    OS/2 supplies drivers for parallel ports and serial ports that let you
  5733.    control printers, plotters, modems, and other devices for hard copy output
  5734.    or communication. Parallel ports are so named because they transfer a
  5735.    byte──8 bits──in parallel to the destination device over 8 separate
  5736.    physical paths (plus additional status and handshaking signal wires). The
  5737.    serial port also communicates with the CPU using bytes, but it sends data
  5738.    to or receives data from its destination device serially──a bit at a
  5739.    time──over a single physical connection in each direction (plus a ground
  5740.    and optional handshaking signal wires).
  5741.  
  5742.    On personal computers, parallel ports are typically used for high-speed
  5743.    output devices, such as line printers, over relatively short distances
  5744.    (less than 50 feet). They are rarely used for devices that require two-way
  5745.    communication with the computer. Serial ports are used for lower-speed
  5746.    devices, such as modems and terminals that require two-way communication
  5747.    (although some printers also have serial interfaces). A serial port can
  5748.    drive a device reliably over much longer distances without extra hardware.
  5749.  
  5750.    The standard version of OS/2 includes drivers for three parallel adapters
  5751.    and for two serial adapters on the PC/AT (three on the PS/2). The logical
  5752.    names for these devices are LPT1 (which also has the alias PRN), LPT2,
  5753.    LPT3, COM1, COM2, and COM3. The device driver for the parallel ports,
  5754.    PRINT01.SYS or PRINT02.SYS, is always loaded during system initialization.
  5755.    The serial port driver, COM01.SYS or COM02.SYS, is loaded only if the user
  5756.    adds the appropriate DEVICE= statement to the CONFIG.SYS file in the root
  5757.    directory on the system boot disk.
  5758.  
  5759.  
  5760.  Sending Output to the Printer
  5761.  
  5762.    To send text and control codes to the printer, you must first call DosOpen
  5763.    with the logical device name of a printer (PRN, LPT1, LPT2, or LPT3); a
  5764.    handle is returned if the printer is available. You can use the deny-all
  5765.    sharing parameter for DosOpen to ensure that another process will not be
  5766.    permitted to use the printer at the same time. (This precaution is
  5767.    unnecessary if the print spooler is running.) Unlike DOS, OS/2 provides no
  5768.    predefined standard list device handle.
  5769.  
  5770.    When the printer is opened, DosWrite can be called as many times as
  5771.    necessary; a call to this function requires that you supply the handle,
  5772.    the address of data to be written, the length of the data (0─65,535
  5773.    bytes), and the address of a variable (Figure 7-1). When DosWrite
  5774.    returns, it places the actual number of bytes written to the printer in
  5775.    the specified variable; this number is always the same as the number
  5776.    requested, barring some unexpected system error. Control characters and
  5777.    escape sequences are passed to the printer unfiltered and unchanged.
  5778.    (DosWrite always writes to character devices other than the standard
  5779.    output in binary mode.)
  5780.  
  5781.    ──────────────────────────────────────────────────────────────────────────
  5782.    pname   db      'LPT1',0        ; device name
  5783.    phandle dw      ?               ; receives device handle
  5784.  
  5785.    action  dw      ?               ; receives DosOpen action
  5786.  
  5787.    msg     db      'HELLO'         ; text of message
  5788.    msg_len equ     $-msg           ; length of message
  5789.  
  5790.    wlen    dw      ?               ; receives bytes written
  5791.  
  5792.            .
  5793.            .
  5794.            .
  5795.            push    ds              ; device name address
  5796.            push    offset DGROUP:pname
  5797.            push    ds              ; receives handle
  5798.            push    offset DGROUP:phandle
  5799.            push    ds              ; receives DosOpen action
  5800.            push    offset DGROUP:action
  5801.            push    0               ; filesize (N/A)
  5802.            push    0
  5803.            push    0               ; attribute (N/A)
  5804.            push    1h              ; flag: open, no create
  5805.            push    41h             ; mode: write-only, deny-none
  5806.            push    0               ; reserved DWORD 0
  5807.            push    0
  5808.            call    DosOpen         ; transfer to OS/2
  5809.            or      ax,ax           ; did open succeed?
  5810.            jnz     error           ; jump if function failed
  5811.            .
  5812.            .
  5813.            .
  5814.            push    phandle         ; handle for LPT1
  5815.            push    ds              ; message address
  5816.            push    offset DGROUP:msg
  5817.            push    msg_len         ; message length
  5818.            push    ds              ; receives bytes written
  5819.            push    offset DGROUP:wlen
  5820.            call    DosWrite        ; transfer to OS/2
  5821.            or      ax,ax           ; did write succeed?
  5822.            jnz     error           ; jump if function failed
  5823.            .
  5824.            .
  5825.            .
  5826.    ──────────────────────────────────────────────────────────────────────────
  5827.  
  5828.    Figure 7-1.  Obtaining a handle for the first list device (LPT1) with
  5829.    DosOpen, and then sending a message to the device with DosWrite.
  5830.  
  5831.    An application can initialize and configure the printer or obtain printer
  5832.    status with the Category 5 DosDevIOCtl functions (Figure 7-2). If your
  5833.    program reconfigures the printer, it should first obtain and save the
  5834.    printer's original configuration and then restore that status before
  5835.    terminating.
  5836.  
  5837.    Function      Action
  5838.    Number
  5839.    ──────────────────────────────────────────────────────────────────────────
  5840.    42H          Sets frame control (chars/line, lines/inch)
  5841.    44H          Sets infinite retry
  5842.    46H          Initializes printer
  5843.    48H          Activates font
  5844.    62H          Gets frame control
  5845.    64H          Gets infinite retry
  5846.    66H          Gets printer status
  5847.    69H          Queries active font
  5848.    6AH          Verifies code page and font
  5849.    ──────────────────────────────────────────────────────────────────────────
  5850.  
  5851.    Figure 7-2.  Category 5 DosDevIOCtl functions for control of the printer.
  5852.  
  5853.  
  5854.  The Print Spooler
  5855.  
  5856.    OS/2 includes a true print spooler that lets the user run multiple
  5857.    processes that generate printer output simultaneously. Using the print
  5858.    spooler is optional; to launch it in OS/2 1.0, issue a RUN directive in
  5859.    CONFIG.SYS or a START command. In OS/2 1.1, the spooler is installed as a
  5860.    default and can be disabled with the Control Panel. The spooler runs as a
  5861.    normal process in user mode and uses only documented API functions.
  5862.  
  5863.    When the print spooler is active, data that is logically written to the
  5864.    printer (from the application's point of view) might be buffered within
  5865.    the system for an unpredictable length of time. The spooler stores the
  5866.    output in a file at least until a DosClose is issued for the printer
  5867.    handle or until the process terminates, and then it adds the name of the
  5868.    file to its spool queue.
  5869.  
  5870.    The print spooler sends the contents of the file onward to the printer
  5871.    when the printer is on-line and when all previous files in the spool queue
  5872.    have been processed. If you are designing an application program, take a
  5873.    conservative approach with data that is "printed"; do not delete it from
  5874.    other storage unless it can be regenerated easily──in case the system
  5875.    crashes or is turned off before the spool queue is emptied.
  5876.  
  5877.    For the purposes of this book, the spooler is of interest mainly as an
  5878.    illustration of the power of the device monitor concept (see also Chapter
  5879.    18). Under normal circumstances (when the print spooler is not loaded),
  5880.    data written by an application to the printer follows a simple and
  5881.    predictable path through the system (Figure 7-3). That is, the data is
  5882.    passed from the application to the OS/2 kernel by DosWrite calls, the
  5883.    kernel passes the data to the printer device driver in "write request
  5884.    packets," and the driver issues commands to the parallel port's hard-wired
  5885.    I/O addresses.
  5886.  
  5887.    When the print spooler is loaded, this simple data pathway changes. The
  5888.    spooler registers itself as a monitor with the printer driver and becomes
  5889.    an integral part of the driver's internal data stream. The driver collects
  5890.    the characters received from the OS/2 kernel into data packets that are
  5891.    passed through the buffers of all the monitors registered with the driver.
  5892.    The last monitor returns the packets to the driver, which then sends the
  5893.    data to the printer. Special packets are also sent to the monitors when
  5894.    the driver receives "open" and "close" commands from the kernel on behalf
  5895.    of an application.
  5896.  
  5897.    ┌──────────────┐
  5898.    │ Application  │
  5899.    └──────┬───────┘
  5900.           │ DosWrite or DosDevIOCtl
  5901.    ┌─────────────┐
  5902.    │ DOSCALL1.DLL │
  5903.    └──────┬───────┘
  5904.           │ Private call interface
  5905.    ┌─────────────┐
  5906.    │     OS/2     │
  5907.    │    kernel    │
  5908.    └──────┬───────┘
  5909.           │ Request packet interface
  5910.    ┌─────────────┐
  5911.    │   Printer    │
  5912.    │device driver │
  5913.    └──────┬───────┘
  5914.           │ Hardware-dependent I/O operations
  5915.    ┌─────────────┐
  5916.    │   Parallel   │
  5917.    │ port adapter │
  5918.    └──────────────┘
  5919.  
  5920.    Figure 7-3.  The flow of printer data when the spooler is not loaded is
  5921.    straightforward. The DosWrite calls issued by an application are passed by
  5922.    DOSCALL1.DLL to the kernel. The kernel translates the DosWrite request
  5923.    into driver request packets, which are shipped to the printer driver. The
  5924.    driver processes the packets and transmits the data to the parallel port
  5925.    adapter.
  5926.  
  5927.    The job of the spooler is thus quite straightforward. When it sees an
  5928.    "open" packet, it creates a temporary spool file and consumes the
  5929.    subsequent data packets, writing the data to the file with ordinary
  5930.    DosWrite operations (Figure 7-4 on the following page). When the spooler
  5931.    sees a monitor "close" packet, it closes the spool file and adds the
  5932.    filename to its spool queue. When the spool file reaches the head of the
  5933.    queue, the spooler opens it, fetches its contents with DosRead, and stuffs
  5934.    the data back into the printer monitor chain; after traversing the buffers
  5935.    of any other monitors, the data arrives back at the printer driver and is
  5936.    written to the physical device. After the entire spool file is processed,
  5937.    the spooler deletes it.
  5938.  
  5939.    ┌──────────────┐                      ┌──────────────────┐
  5940.    │ Application  │                      │     Spooler      │
  5941.    └──────┬───────┘                      │ (Device monitor) │
  5942.           │ DosWrite or DosDevIOCtl      └─────┬────────┬─┘
  5943.    ┌─────────────┐                        │    │   │     │
  5944.    │ DOSCALL1.DLL │                        │    │   │     │
  5945.    └──────┬───────┘                        │  ┌────┴─┐   │
  5946.           │ Private call interface         │  │ Spool │   │
  5947.    ┌─────────────┐                        │  │ file  │   │
  5948.    │     OS/2     │                        │  └───────┘   │
  5949.    │    kernel    │                        │              │
  5950.    └──────┬───────┘                        │              │
  5951.           │ Request packet interface       │              │
  5952.    ┌─────────────┐                        │              │
  5953.    │   Printer    ├────────────────────────┘              │
  5954.    │device driver ───────────────────────────────────────┘
  5955.    └──────┬───────┘
  5956.           │ Hardware-dependent I/O operations
  5957.    ┌─────────────┐
  5958.    │   Parallel   │
  5959.    │ port adapter │
  5960.    └──────────────┘
  5961.  
  5962.    Figure 7-4.  The flow of printer data when the spooler is loaded. The
  5963.    spooler is a process that registers as a device monitor with the printer
  5964.    driver. Data sent to the printer driver by the kernel passes through the
  5965.    chain of monitor buffers. The spooler removes the data from the chain and
  5966.    writes it into a temporary file. When the printer is available, the
  5967.    spooler reads the file back into memory and inserts the data into the
  5968.    monitor chain so that the driver can transfer it to the physical device.
  5969.  
  5970.  
  5971.  Serial Port Input and Output
  5972.  
  5973.    To use a serial port, an application must first call DosOpen with the
  5974.    logical device name of the desired port (COM1, COM2, or COM3). To claim
  5975.    exclusive use of a serial port and prevent interference by other
  5976.    processes, specify deny-all sharing mode in the DosOpen call. If the port
  5977.    is available, DosOpen returns a handle. As with the printer, OS/2 provides
  5978.    no predefined standard auxiliary device handle for the serial port that is
  5979.    equivalent to the handle supplied to applications under DOS.
  5980.  
  5981.    The general-purpose functions DosRead and DosWrite are used for input and
  5982.    output. They require familiar parameters──a handle, the address and length
  5983.    of a data buffer, and a variable to receive the actual number of bytes
  5984.    read or written. Figure 7-5 offers an example. The driver services
  5985.    DosRead and DosWrite requests independently, in the order that they are
  5986.    received; transmit and receive operations can be (and frequently are) in
  5987.    progress simultaneously.
  5988.  
  5989.    ──────────────────────────────────────────────────────────────────────────
  5990.    cname   db      'COM1',0        ; device name
  5991.    chandle dw      ?               ; receives handle
  5992.  
  5993.    action  dw      ?               ; receives DosOpen action
  5994.  
  5995.    msg     db      'HELLO'         ; text of message
  5996.    msg_len equ     $-msg           ; length of message
  5997.  
  5998.    wlen    dw      ?               ; receives bytes written
  5999.  
  6000.            .
  6001.            .
  6002.            .
  6003.            push    ds              ; device name address
  6004.            push    offset DGROUP:cname
  6005.            push    ds              ; receives device handle
  6006.            push    offset DGROUP:chandle
  6007.            push    ds              ; receives DosOpen action
  6008.            push    offset DGROUP:action
  6009.            push    0               ; filesize (N/A)
  6010.            push    0
  6011.            push    0               ; attribute (N/A)
  6012.            push    1h              ; flag: open, no create
  6013.            push    12h             ; mode: read/write, deny-all
  6014.            push    0               ; reserved DWORD 0
  6015.            push    0
  6016.            call    DosOpen         ; transfer to OS/2
  6017.            or      ax,ax           ; did open succeed?
  6018.            jnz     error           ; jump if function failed
  6019.            .
  6020.            .
  6021.            .
  6022.            push    chandle         ; handle for COM1
  6023.            push    ds              ; message address
  6024.            push    offset DGROUP:msg
  6025.            push    msg_len         ; message length
  6026.            push    ds              ; receives bytes written
  6027.            push    offset DGROUP:wlen
  6028.            call    DosWrite        ; transfer to OS/2
  6029.            or      ax,ax           ; did write succeed?
  6030.            jnz     error           ; jump if function failed
  6031.            .
  6032.            .
  6033.            .
  6034.    ──────────────────────────────────────────────────────────────────────────
  6035.  
  6036.    Figure 7-5.  Obtaining a handle for COM1 with DosOpen, and then sending a
  6037.    message to it with DosWrite.
  6038.  
  6039.    The serial port driver is fully interrupt-driven and buffers characters
  6040.    internally. Thus, completion of a DosWrite operation (from the
  6041.    application's point of view) does not necessarily mean that the characters
  6042.    have been physically transmitted. The sizes of the driver's internal
  6043.    buffers are subject to change from version to version and may even (in a
  6044.    future version) be adjusted dynamically as a function of system activity.
  6045.    In OS/2 version 1.1, the receive buffer is 1024 bytes and the transmit
  6046.    buffer is 128 bytes.
  6047.  
  6048.    Handles for the serial port are always read and written in binary mode;
  6049.    ordinarily, control characters and escape sequences do not receive any
  6050.    special treatment and are passed through unchanged. If a process enables
  6051.    flow control, however, the XON and XOFF character codes are intercepted
  6052.    and processed by the driver.
  6053.  
  6054.    When the driver receives an XOFF from the serial port, it stops
  6055.    transmitting until it receives an XON code. Similarly, when its receive
  6056.    buffer is nearly full, the driver sends an XOFF to the serial port; when
  6057.    calls to DosRead from an application program empty the buffer to half-full
  6058.    or less, the driver sends an XON to the serial port so that the remote
  6059.    device can resume transmission. The default XON code is 11H (Ctrl-Q) and
  6060.    the default XOFF code is 13H (Ctrl-S), but your program can set each to
  6061.    another value.
  6062.  
  6063.  
  6064.  Configuring the Serial Port
  6065.  
  6066.    OS/2 provides protected mode applications with a comprehensive array of
  6067.    Category 1 DosDevIOCtl functions to control a number of options, among
  6068.    them the serial port baud rate, parity, character length, stop bits,
  6069.    timeout delays, and flow control (XON/XOFF processing). Functions are also
  6070.    available to check the serial port status and the configuration, the size
  6071.    of the serial port driver's receive and transmit buffers, and the number
  6072.    of characters waiting in those buffers.
  6073.  
  6074.    For those applications that must control modems or perform hardware
  6075.    handshaking, the following signals at the serial port connector can be
  6076.    controlled by DosDevIOCtl calls:
  6077.  
  6078.    ■  Data Terminal Ready (DTR)
  6079.  
  6080.    ■  Request to Send (RTS)
  6081.  
  6082.    Similarly, an application can use DosDevIOCtl calls to query the state of
  6083.    the following incoming signals at the serial port connector:
  6084.  
  6085.    ■  Data Set Ready (DSR)
  6086.  
  6087.    ■  Clear to Send (CTS)
  6088.  
  6089.    ■  Data Carrier Detect (DCD)
  6090.  
  6091.    ■  Ring Indicator (RI)
  6092.  
  6093.    The DosDevIOCtl functions for the serial port driver (Category 1) are
  6094.    summarized in Figure 7-6.
  6095.  
  6096.    Take care when reconfiguring the serial port "on the fly." The serial port
  6097.    driver processes DosDevIOCtl requests immediately upon receipt and does
  6098.    not attempt to serialize them with read and write requests; thus, the
  6099.    results can be unpredictable if the driver receives (for example) a
  6100.    DosDevIOCtl command to change the baud rate while it is in the process of
  6101.    receiving or transmitting data.
  6102.  
  6103.    The default configuration for each serial port, established during system
  6104.    initialization, is 1200 baud, 7-bit character length, even parity, and 1
  6105.    stop bit. The DTR and RTS signals are initially off. They are turned on
  6106.    when the COM device is opened by an application, and they are turned off
  6107.    again when the COM device is closed and the transmit buffer is empty. Many
  6108.    other factors, however, can affect these signals during serial port
  6109.    communication.
  6110.  
  6111. ╓┌─┌─────────────┌───────────────────────────────────────────────────────────╖
  6112.    Function      Action
  6113.    Function      Action
  6114.    Number
  6115.    ──────────────────────────────────────────────────────────────────────────
  6116.    41H          Sets baud rate
  6117.    42H          Sets line characteristics (data bits, parity, stop bits)
  6118.    44H          Transmits byte immediate
  6119.    45H          Sets break off
  6120.    46H          Sets modem control signals (DTR, RTS)
  6121.    47H          Stops transmitting (as if XOFF received)
  6122.    48H          Starts transmitting (as if XON received)
  6123.    4BH          Sets break on
  6124.    53H          Sets device control block
  6125.    61H          Gets baud rate
  6126.    62H          Gets line characteristics (data bits, parity, stop bits,
  6127.                  break status)
  6128.    64H          Gets COM status
  6129.    65H          Gets transmit data status
  6130.    66H          Gets modem output control signals (DTR, RTS)
  6131.    67H          Gets modem input control signals (DSR, CTS, DCD, RI)
  6132.    68H          Gets number of characters in receive buffer
  6133.    69H          Gets number of characters in transmit buffer
  6134.    Function      Action
  6135.    Number
  6136.    ──────────────────────────────────────────────────────────────────────────
  6137.   69H          Gets number of characters in transmit buffer
  6138.    6DH          Gets COM error information
  6139.    72H          Gets COM event information
  6140.    73H          Gets device control block
  6141.    ──────────────────────────────────────────────────────────────────────────
  6142.  
  6143.  
  6144.    Figure 7-6.  Category 1 DosDevIOCtl functions for control of the serial
  6145.    port.
  6146.  
  6147.    To examine a more complete example of serial port programming under OS/2,
  6148.    see the DUMBTERM.C or DUMBTERM.ASM program in Chapter 12. This is a
  6149.    simple "dumb terminal" program that takes advantage of multiple threads to
  6150.    avoid the need for polling. DUMBTERM configures the serial port according
  6151.    to #define or equ statements in the source file; then it exchanges
  6152.    characters between the console (keyboard and video display) and the serial
  6153.    port until it receives an exit command.
  6154.  
  6155.  
  6156.  The Serial Port and Real Mode Applications
  6157.  
  6158.    The job of OS/2's serial port driver is complicated by the need to support
  6159.    the DOS compatibility environment. Because the DOS and ROM BIOS serial
  6160.    port drivers do not buffer data and are not interrupt-driven, most real
  6161.    mode applications control the serial port adapter directly and bypass
  6162.    operating system services completely.
  6163.  
  6164.    The driver uses some fairly simple strategies to arbitrate serial port
  6165.    usage between protected mode and real mode applications. It clears out the
  6166.    words in the ROM BIOS data area that normally contain the base port
  6167.    addresses of the serial adapters (location 0040:0000H for COM1, 0040:0002H
  6168.    for COM2). Consequently, real mode applications that test for the
  6169.    existence of the serial port by inspecting the ROM BIOS data area or by
  6170.    calling the ROM BIOS serial port driver will conclude that the adapter is
  6171.    not present. The OS/2 real mode command SETCOM40 updates the ROM BIOS data
  6172.    area to indicate the presence of a port.
  6173.  
  6174.    A real mode application can acquire a COM device in a number of ways: It
  6175.    can perform an Int 21H read, write, or IOCtl function call using the
  6176.    predefined handle (3) for the first serial port device, or it can
  6177.    explicitly open COM1, COM2, or COM3. Yet another method is to modify the
  6178.    interrupt vector for a serial port. OS/2 periodically checks the interrupt
  6179.    vector table; if it detects that the vector for a serial port has been
  6180.    changed, it assumes that the serial port has been claimed by the real mode
  6181.    application. When a real mode program is using a serial port, OS/2 fails
  6182.    all DosOpen requests by protected mode applications for that port until
  6183.    the real mode application terminates, closes the device, or restores the
  6184.    interrupt vector.
  6185.  
  6186.    If a protected mode program owns a COM port, and a real mode application
  6187.    attempts to open it or capture the interrupt vector, the user is notified
  6188.    by a critical error pop-up. The user can then elect to terminate the real
  6189.    mode application, ignore the error and continue, or (in the case of an
  6190.    attempted open) retry the operation.
  6191.  
  6192.  
  6193.  
  6194.  ────────────────────────────────────────────────────────────────────────────
  6195.  SECTION 3  PROGRAMMING MASS STORAGE
  6196.  ────────────────────────────────────────────────────────────────────────────
  6197.  
  6198.  
  6199.  
  6200.  ────────────────────────────────────────────────────────────────────────────
  6201.  Chapter 8  File Management
  6202.  
  6203.    Although the face of an application is the dexterity with which it
  6204.    manipulates the display, its heart is the set of routines that fetch,
  6205.    manipulate, and store data on media such as magnetic disks. The data on
  6206.    disk is organized into files, which are identified by name; files in turn
  6207.    can be organized by being grouped into directories; directories and files
  6208.    are stored on logical volumes. Applications concern themselves only with
  6209.    the names and contents of files; the details of a file's physical location
  6210.    on the disk and its retrieval are the province of the operating system.
  6211.  
  6212.    OS/2 provides applications with a variety of services to manipulate files,
  6213.    directories, and volumes in a hardware-independent manner. This chapter
  6214.    explains the OS/2 functions that create, open, close, rename, and delete
  6215.    disk files; read data from and write data to disk files; and inspect or
  6216.    change the information associated with filenames in disk directories
  6217.    (Figure 8-1 on the following page). Directories and volumes are covered
  6218.    in Chapter 9; the physical structure of OS/2 file systems is covered in
  6219.    Chapter 10.
  6220.  
  6221.    ──────────────────────────────────────────────────────────────────────────
  6222.    NOTE:
  6223.      The discussion of disk storage in this book centers on the so-called FAT
  6224.      (file allocation table) file systems, which are identical to those used
  6225.      by MS-DOS. Although OS/2 is designed to allow installation of other file
  6226.      systems as it evolves, the benefits of media compatibility with the vast
  6227.      installed base of MS-DOS machines guarantee that FAT file systems will
  6228.      prevail in OS/2 for a long time to come.
  6229.    ──────────────────────────────────────────────────────────────────────────
  6230.  
  6231. ╓┌─┌─────────────────────┌───────────────────────────────────────────────────╖
  6232.    Function              Description
  6233.    ──────────────────────────────────────────────────────────────────────────
  6234.    File Operations
  6235.    DosBufReset          Flushes file buffers to disk and updates directory
  6236.    DosClose             Flushes file buffers to disk, updates directory
  6237.                          entry, and releases handle
  6238.    DosDelete            Removes a file from the directory
  6239.    Function              Description
  6240.    ──────────────────────────────────────────────────────────────────────────
  6241.   DosDelete            Removes a file from the directory
  6242.    DosDupHandle         Returns a new handle that duplicates an existing
  6243.                          handle, or forces one handle to track another
  6244.    DosMove              Renames and/or moves a file (also renames
  6245.                          directories)
  6246.    DosNewSize           Truncates or extends an existing file
  6247.    DosOpen              Opens, creates, or replaces a file, returning a
  6248.                          handle
  6249.    DosSetMaxFH          Sets maximum handles for process
  6250.  
  6251.    I/O Operations
  6252.    DosChgFilePtr        Sets file pointer relative to start of file, current
  6253.                          position, or end of file
  6254.    DosFileLocks         Locks or unlocks file region
  6255.    DosRead              Reads from file, device, or pipe (synchronous)
  6256.    DosReadAsync         Reads from file, device, or pipe (asynchronous)
  6257.    DosWrite             Writes to file, device, or pipe (synchronous)
  6258.    DosWriteAsync        Writes to file, device, or pipe (asynchronous)
  6259.  
  6260.    Function              Description
  6261.    ──────────────────────────────────────────────────────────────────────────
  6262. 
  6263.    File Information
  6264.    DosQFHandState       Returns handle characteristics
  6265.    DosQFileInfo         Returns file date, time, attributes, and size
  6266.    DosQFileMode         Returns file attributes
  6267.    DosQHandType         Returns file, pipe, or device code for a handle
  6268.    DosSetFHandState     Sets handle characteristics
  6269.    DosSetFileInfo       Sets file date and time
  6270.    DosSetFileMode       Sets file attributes
  6271.    ──────────────────────────────────────────────────────────────────────────
  6272.  
  6273.  
  6274.    Figure 8-1.  OS/2 file management services at a glance.
  6275.  
  6276.  
  6277.  Filenames and Handles
  6278.  
  6279.    To manipulate a file, a program must identify it to the operating system
  6280.    with a null-terminated (ASCIIZ) character string called a pathname. A
  6281.    pathname can contain four different elements in the following form:
  6282.  
  6283.      drive:path\filename.extension
  6284.  
  6285.    The drive code takes the form of a single letter followed by a colon (:)
  6286.    and identifies the logical volume that holds the file. The path identifies
  6287.    the directory that contains the filename; it can be a complete
  6288.    specification from the root directory of the volume, or it can be a
  6289.    relative specification (more about this in Chapter 9). If drive is
  6290.    absent, the current drive is assumed; if path is absent, the current
  6291.    directory is assumed. For each process, the system maintains a current
  6292.    drive and (for each drive) a current directory.
  6293.  
  6294.    The filename is the only component that must be present in a pathname. It
  6295.    contains a maximum of eight characters. The extension, when present, is
  6296.    really part of the filename; it appends a maximum of three characters,
  6297.    separated from the filename by a period. (These restrictions on filenames
  6298.    and extensions might not apply in non-FAT file systems.) Although
  6299.    extensions generally signify a file's "type" or contents, this is only a
  6300.    convention.
  6301.  
  6302.    Functions that operate on a file as a whole, such as those that delete or
  6303.    rename files, require only the ASCIIZ pathname. Functions that operate on
  6304.    the contents of files, however, refer to files with handles. A handle is a
  6305.    16-bit token──usually a small positive integer──that OS/2 provides to a
  6306.    program for its use in manipulating the associated file. (In a few
  6307.    moments, you'll see how a program obtains handles.) The value of a handle
  6308.    has no special significance, except that it is different from the values
  6309.    of handles for other files or devices being used at the same time by the
  6310.    same process.
  6311.  
  6312.    The default limit on the number of handles that a single process can own
  6313.    is 20. Three handles are preassigned to the standard input, standard
  6314.    output, and standard error devices (mentioned earlier in Chapters 5 and
  6315.    6 in connection with the keyboard and video display). A process can
  6316.    inherit additional handles from its parent, further infringing on the
  6317.    initial allotment of 20. Fortunately, you can increase the per-process
  6318.    handle limit at run time with the function DosSetMaxFH.
  6319.  
  6320.  
  6321.  Creating, Opening, and Closing Files
  6322.  
  6323.    The API function DosOpen is the bridge between the functions that use
  6324.    pathnames and the functions that use handles. It accepts the pathname of a
  6325.    file to be created, opened, or replaced, and it returns a handle that can
  6326.    be used to manipulate the contents or attributes of the file. As we saw in
  6327.    Chapters 5─7, you can also use DosOpen to open character devices for
  6328.    input and output as though they were files.
  6329.  
  6330.    The DosOpen function is flexible; consequently, it requires many
  6331.    parameters and is the most complex file management function to master.
  6332.    Your program must supply DosOpen with the following information:
  6333.  
  6334.    ■  The address of an ASCIIZ pathname for the file or device
  6335.  
  6336.    ■  The OpenFlag and OpenMode parameters
  6337.  
  6338.    ■  A file size
  6339.  
  6340.    ■  A file attribute
  6341.  
  6342.    ■  The addresses of two variables that will receive information from OS/2
  6343.  
  6344.    The OpenFlag parameter has two fields that allow the DosOpen operation to
  6345.    be tailored to the previous existence or nonexistence of the file (Figure
  6346.    8-2). For example, the combination "create if file does not exist" and
  6347.    "fail if file exists" amounts to a "test and lock" operation that can be
  6348.    used to implement semaphores in the form of files across a network.
  6349.  
  6350.    The OpenMode parameter controls a number of characteristics of the open
  6351.    file. It governs the process's access to the file, the manner of error
  6352.    handling and data buffering to be used, the direct disk access option (see
  6353.    Chapter 10), the level at which the file can be shared with other
  6354.    processes, and whether access to the file is inherited by child processes
  6355.    (Figure 8-3). If one or more other processes have already opened the same
  6356.    file, the outcome of the DosOpen operation depends on the requested access
  6357.    rights and sharing mode as shown in Figure 8-4 on p. 136.
  6358.  
  6359.    A process should ensure that its own DosOpen requests succeed in all
  6360.    appropriate conditions by specifying the minimum necessary access rights;
  6361.    for example, the process should not request read/write access when it only
  6362.    needs to read the file. Similarly, the process should avoid requesting
  6363.    restrictive sharing modes whenever possible.
  6364.  
  6365.    The DosOpen attribute and size parameters are relevant only when a file is
  6366.    being created or replaced. The attribute parameter marks the file as
  6367.    hidden, system, read-only, or modified since last backup (archive), as
  6368.    shown in Figure 8-5 on p. 136. The size parameter preallocates disk space
  6369.    for the file; the data in the preallocated area is undefined. (Later
  6370.    versions of OS/2 might attempt to preallocate contiguous disk space to
  6371.    improve file access times.) When an existing file is opened, the attribute
  6372.    and size parameters are ignored.
  6373.  
  6374.     F  E  D  C  B  A  9  8  7  6  5  4  3  2  1  0
  6375.    ┌──────────────────────┬───────────┬───────────┐
  6376.    │                      │           │           │
  6377.    └───────────┬──────────┴─────┬─────┴─────┬─────┘
  6378.                │                │           └─ Action to take if file exists:
  6379.                │                │              0000 = fail
  6380.                │                │              0001 = open file
  6381.                │                │              0010 = replace file
  6382.                │                │
  6383.                │                └─ Action to take if file does not exist
  6384.                │                   0000 = fail
  6385.                │                   0001 = create file
  6386.                │
  6387.                └─ Reserved
  6388.  
  6389.    Figure 8-2.  OpenFlag parameter for the DosOpen function.
  6390.  
  6391.     F  E  D  C  B  A  9  8  7  6  5  4  3  2  1  0
  6392.    ┌──┬──┬──┬─────────────┬──┬────────┬──┬────────┐
  6393.    │  │  │  │             │  │        │  │        │
  6394.    └┬─┴┬─┴┬─┴──────┬──────┴┬─┴───┬────┴┬─┴────┬───┘
  6395.     │  │  │        │       │     │     │      │
  6396.     │  │  │        │       │     │     │      └─ Access mode
  6397.     │  │  │        │       │     │     │         000 = read-only
  6398.     │  │  │        │       │     │     │         001 = write-only
  6399.     │  │  │        │       │     │     │         010 = read/write
  6400.     │  │  │        │       │     │     │
  6401.     │  │  │        │       │     │     └─ Reserved
  6402.     │  │  │        │       │     │
  6403.     │  │  │        │       │     └─ Sharing mode
  6404.     │  │  │        │       │        001 = deny-all
  6405.     │  │  │        │       │        010 = deny-write
  6406.     │  │  │        │       │        011 = deny-read
  6407.     │  │  │        │       │        100 = deny-none
  6408.     │  │  │        │       │
  6409.     │  │  │        │       └─ Inheritance flag
  6410.     │  │  │        │          0 = handle inherited by child processes
  6411.     │  │  │        │          1 = handle not inherited
  6412.     │  │  │        │
  6413.     │  │  │        └─ Reserved
  6414.     │  │  │
  6415.     │  │  └─ Critical error mode
  6416.     │  │     0 = reported by system pop-ups
  6417.     │  │     1 = returned to process
  6418.     │  │
  6419.     │  └─ Write-through flag
  6420.     │     0 = writes may be deferred
  6421.     │     1 = snychronous physical writes required
  6422.     │
  6423.     └─ DASD access mode
  6424.        0 = file access
  6425.        1 = direct disk access
  6426.  
  6427.    Figure 8-3.  OpenMode parameter for the DosOpen function. Use of DosOpen
  6428.    with the DASD bit set for direct disk access is discussed in Chapter 10.
  6429.  
  6430.    If the DosOpen operation succeeds, the function returns zero in register
  6431.    AX. The file handle, to be used for subsequent read, write, seek, and
  6432.    close operations, is returned in one of the variables. An "action" code is
  6433.    returned in the other variable: 0001 if the file existed and was opened,
  6434.    0002 if the file was created, or 0003 if the file existed and was replaced
  6435.    (the previous data in the file was lost).
  6436.  
  6437.    If the call to DosOpen fails, an error code is returned in AX. The
  6438.    function call can fail for many reasons, including bad pathnames or other
  6439.    parameters, sharing conflicts with another process, inadequate access
  6440.    rights (to a network directory, for example), and exhaustion of process or
  6441.    system resources (such as handles).
  6442.  
  6443.    ┌──────────────┐ ┌───────────────────────────────────────┐
  6444.    │ Requesting   │ │     Other Process's Access Rights     │
  6445.    │ Process's    │ │                                       │
  6446.    │ Sharing Mode │ │ Read-only    Write-only    Read/Write │
  6447.    │              │ └───────────────────────────────────────┘
  6448.    │              │ ╔═══════════╗ ╔═══════════╗ ╔═══════════╗
  6449.    │ Deny-all     │ ║   Fail    ║ ║   Fail    ║ ║   Fail    ║
  6450.    │              │ ╚═══════════╝ ╚═══════════╝ ╚═══════════╝
  6451.    │              │ ┌───────────┐ ╔═══════════╗ ╔═══════════╗
  6452.    │ Deny-write   │ │  Succeed  │ ║   Fail    ║ ║   Fail    ║
  6453.    │              │ └───────────┘ ╚═══════════╝ ╚═══════════╝
  6454.    │              │ ╔═══════════╗ ┌───────────┐ ╔═══════════╗
  6455.    │ Deny-read    │ ║   Fail    ║ │  Succeed  │ ║   Fail    ║
  6456.    │              │ ╚═══════════╝ └───────────┘ ╚═══════════╝
  6457.    │              │ ┌───────────┐ ┌───────────┐ ┌───────────┐
  6458.    │ Deny-none    │ │  Succeed  │ │  Succeed  │ │  Succeed  │
  6459.    └──────────────┘ └───────────┘ └───────────┘ └───────────┘
  6460.                            Outcome of call to DosOpen
  6461.    ┌──────────────┐ ┌─────────────────────────────────────────────────────┐
  6462.    │ Requesting   │ │            Other Process's Sharing Mode             │
  6463.    │ Process's    │ │                                                     │
  6464.    │ Access Rights│ │ Deny-all     Deny-write     Deny-read     Deny-none │
  6465.    │              │ └─────────────────────────────────────────────────────┘
  6466.    │              │ ╔═══════════╗ ┌───────────┐ ╔═══════════╗ ┌───────────┐
  6467.    │ Read-only    │ ║   Fail    ║ │  Succeed  │ ║   Fail    ║ │  Succeed  │
  6468.    │              │ ╚═══════════╝ └───────────┘ ╚═══════════╝ └───────────┘
  6469.    │              │ ╔═══════════╗ ╔═══════════╗ ┌───────────┐ ┌───────────┐
  6470.    │ Write-only   │ ║   Fail    ║ ║   Fail    ║ │  Succeed  │ │  Succeed  │
  6471.    │              │ ╚═══════════╝ ╚═══════════╝ └───────────┘ └───────────┘
  6472.    │              │ ╔═══════════╗ ╔═══════════╗ ╔═══════════╗ ┌───────────┐
  6473.    │ Read/Write   │ ║   Fail    ║ ║   Fail    ║ ║   Fail    ║ │  Succeed  │
  6474.    └──────────────┘ ╚═══════════╝ ╚═══════════╝ ╚═══════════╝ └───────────┘
  6475.                                   Outcome of call to DosOpen
  6476.  
  6477.    Figure 8-4.  Outcome of DosOpen based on sharing mode and access rights of
  6478.    the requesting process and those of another process that previously opened
  6479.    the file.
  6480.  
  6481.     F  E  D  C  B  A  9  8  7  6  5  4  3  2  1  0
  6482.    ┌────────────────────────────┬──┬─────┬──┬──┬──┐
  6483.    │                            │  │     │  │  │  │
  6484.    └─────────────┬──────────────┴┬─┴──┬──┴┬─┴┬─┴┬─┘
  6485.                  │               │    │   │  │  │
  6486.                  │               │    │   │  │  └─ Read-only
  6487.                  │               │    │   │  │
  6488.                  │               │    │   │  └──── Hidden
  6489.                  │               │    │   │
  6490.                  │               │    │   └─────── System
  6491.                  │               │    │
  6492.                  │               │    └─────────── Reserved
  6493.                  │               │
  6494.                  │               └──────────────── Archive
  6495.                  │
  6496.                  └──────────────────────────────── Reserved
  6497.  
  6498.    Figure 8-5.  Attribute parameter for the DosOpen function when it is used
  6499.    to create a file. Do not use the read-only attribute bit with DosOpen; you
  6500.    can apply it to the file later with DosSetFileMode. Also, do not use bits
  6501.    3 and 4 with DosOpen; these bits, which indicate directories and volume
  6502.    labels in FAT file systems, are discussed further in Chapter 9.
  6503.  
  6504.    DosOpen is a good example of an OS/2 function call that is better suited
  6505.    to high level languages than to assembler. The large number of parameters
  6506.    that must be pushed on the stack bloat the source code (Figure 8-6). You
  6507.    might find it useful to encapsulate DosOpen within a subroutine that
  6508.    assumes reasonable default values for most parameters. If you also write
  6509.    the subroutine so that it places its local variables on the stack,
  6510.    multiple threads can share the subroutine without any need for semaphores
  6511.    or other synchronization mechanisms (Figure 8-7 on the following pages).
  6512.  
  6513.    ──────────────────────────────────────────────────────────────────────────
  6514.    fname   db      'MYFILE.DAT',0  ; ASCIIZ pathname
  6515.    fhandle dw      0               ; receives handle
  6516.    faction dw      0               ; receives DosOpen action
  6517.  
  6518.            .
  6519.            .
  6520.            .
  6521.            push    ds              ; ASCIIZ pathname
  6522.            push    offset DGROUP:fname
  6523.            push    ds              ; receives handle
  6524.            push    offset DGROUP:fhandle
  6525.            push    ds              ; receives DosOpen action
  6526.            push    offset DGROUP:faction
  6527.            push    0               ; file size (ignored)
  6528.            push    0
  6529.            push    0               ; file attribute (ignored)
  6530.            push    1               ; flag: fail if file
  6531.                                    ;       doesn't exist
  6532.            push    42h             ; mode: deny-none,
  6533.                                    ;       access read/write
  6534.            push    0               ; reserved DWORD 0
  6535.            push    0
  6536.            call    DosOpen         ; transfer to OS/2
  6537.            or      ax,ax           ; did open succeed?
  6538.            jnz     error           ; jump if function failed
  6539.            .
  6540.            .
  6541.            .
  6542.    ──────────────────────────────────────────────────────────────────────────
  6543.  
  6544.    Figure 8-6.  Example of in-line code to open a file. When an existing file
  6545.    is opened, the attribute and size parameters are ignored. Compare with
  6546.    Figure 8-7.
  6547.  
  6548.    ──────────────────────────────────────────────────────────────────────────
  6549.    ; FOPEN: reentrant routine to open a file or device
  6550.    ;
  6551.    ; Call with:    DS:DX = address of ASCIIZ pathname
  6552.    ;               AX    = OpenMode
  6553.    ; Returns:      Z     = true if successful
  6554.    ;               BX    = handle
  6555.    ;               AX    = DosOpen action
  6556.    ;               or
  6557.    ;               Z     = false if failed
  6558.    ;               AX    = DosOpen error code
  6559.    ; Uses:         DX
  6560.  
  6561.    fhandle equ     [bp-2]
  6562.    faction equ     [bp-4]
  6563.  
  6564.    fopen   proc    near
  6565.  
  6566.            push    bp              ; save original BP value
  6567.            mov     bp,sp           ; set up stack frame
  6568.            sub     sp,4            ; allocate local variables
  6569.  
  6570.            push    ds              ; ASCIIZ pathname
  6571.            push    dx
  6572.            push    ss              ; receives handle
  6573.            lea     dx,fhandle
  6574.            push    dx
  6575.            push    ss              ; receives DosOpen action
  6576.            lea     dx,faction
  6577.            push    dx
  6578.            push    0               ; file size (ignored)
  6579.            push    0
  6580.            push    0               ; file attribute (ignored)
  6581.            push    1               ; flag: fail if file
  6582.                                    ;       doesn't exist
  6583.            push    ax              ; mode: from caller
  6584.            push    0               ; reserved DWORD 0
  6585.            push    0
  6586.  
  6587.            call    DosOpen         ; transfer to OS/2
  6588.  
  6589.            or      ax,ax           ; set Z flag to indicate
  6590.            jnz     fopen1          ; success or failure
  6591.            mov     bx,fhandle      ; get handle
  6592.            mov     ax,faction      ; get DosOpen action
  6593.  
  6594.    fopen1: mov     sp,bp           ; discard local variables
  6595.            pop     bp              ; restore original BP
  6596.            ret                     ; back to caller
  6597.  
  6598.    fopen   endp
  6599.    ──────────────────────────────────────────────────────────────────────────
  6600.  
  6601.    Figure 8-7.  Example of a reentrant procedure to encapsulate the job of
  6602.    opening a file. Local variables are created on the stack to receive the
  6603.    results of the DosOpen operation. Compare with Figure 8-6.
  6604.  
  6605.    Once a file has been opened or created, a process can use several
  6606.    auxiliary functions to obtain or modify information associated with the
  6607.    file. DosQHandType returns information indicating whether a handle is
  6608.    associated with a file, a pipe, or a character device; DosQFileMode
  6609.    returns a file's attributes; DosQFileInfo returns a file's size,
  6610.    attributes, and date and time stamps; and DosQFHandState returns the
  6611.    write-through, inheritance, sharing, and access rights associated with a
  6612.    file handle. These functions are especially useful to a child process that
  6613.    has inherited a handle and needs to know how that handle may be used. A
  6614.    similar set of functions permits a process to change attributes, access
  6615.    rights, and the like "on the fly."
  6616.  
  6617.    When a process finishes using a file, it calls DosClose to release the
  6618.    handle, flush to disk any unwritten data that is buffered within OS/2, and
  6619.    update the size, date, and time in the directory entry for the file if it
  6620.    has been modified. Under normal circumstances, a DosClose operation fails
  6621.    only if the file was on a floppy disk, the file was modified, and the user
  6622.    removed the disk while the file was open.
  6623.  
  6624.  
  6625.  Reading and Writing Files
  6626.  
  6627.    After a process opens or creates a file, it is ready to do useful work:
  6628.    fetching and displaying information from the file, modifying the data
  6629.    already stored in the file, or adding new data to the file. Let's review
  6630.    some key terms related to file access:
  6631.  
  6632.    ■  Reading and writing
  6633.  
  6634.    ■  The file pointer
  6635.  
  6636.    ■  Sequential and random access
  6637.  
  6638.    ■  Synchronous and asynchronous access
  6639.  
  6640.    Reading is copying data from disk into RAM; the data on the disk is
  6641.    unchanged, and the previous contents of the area of RAM are lost. Writing
  6642.    is copying data from RAM onto disk; the contents of RAM are unchanged, and
  6643.    the previous contents of the disk area are lost. Data is not necessarily
  6644.    physically transferred between the disk and RAM when an application's read
  6645.    or write request is "logically" complete; OS/2 often buffers disk data
  6646.    internally for extended periods to improve performance.
  6647.  
  6648.    OS/2 maintains a file pointer for each handle in the system file table
  6649.    (SFT). When an application issues a read or write request, the current
  6650.    value of the file pointer for that handle determines the byte offset (from
  6651.    the start of the file) at which the transfer will begin. When the transfer
  6652.    is complete, OS/2 updates the file pointer so that it points one byte past
  6653.    the last byte that was read or written. An application can also set the
  6654.    file pointer to an arbitrary value──this is often referred to as a seek
  6655.    operation (not to be confused with the physical seek performed by a disk
  6656.    drive, which involves moving the read/write heads to a specific cylinder).
  6657.  
  6658.    Sequential access is reading or writing consecutive data in a file. This
  6659.    type of processing is typically used for accessing files with
  6660.    variable-length records; a text file, in which each line can be considered
  6661.    a record delimited by a newline character, is an example of such a file. A
  6662.    program that processes files sequentially need not concern itself with the
  6663.    file pointer because OS/2 maintains it.
  6664.  
  6665.    Random access is reading and writing nonsequential records──the physical
  6666.    ordering of the records in the file does not determine the order in which
  6667.    they are processed. (The term is not particularly apt, of course, because
  6668.    the processing is not at all random.) In simple applications, random
  6669.    access is typically used for files that contain fixed-length records. A
  6670.    program can calculate the location of a record (by multiplying a record
  6671.    number times the record size), set the file pointer to that offset, and
  6672.    then request a read or write. The indexed file access methods used by more
  6673.    sophisticated applications are much more versatile.
  6674.  
  6675.    In the classic definition of synchronous access, the process requesting
  6676.    the read or write operation is suspended until the transfer is complete.
  6677.    On the other hand, when asynchronous access is requested, the operating
  6678.    system starts the transfer and then returns control to the process,
  6679.    notifying the process by some other means when the read or write operation
  6680.    is finished.
  6681.  
  6682.    With this terminology in hand, let's take a look at OS/2's five
  6683.    fundamental services for reading and writing files: DosRead, DosReadAsync,
  6684.    DosWrite, DosWriteAsync, and DosChgFilePtr. Figure 8-8 contains a sample
  6685.    code skeleton for file access by an OS/2 application.
  6686.  
  6687.    ──────────────────────────────────────────────────────────────────────────
  6688.    recsize equ     1024            ; file record size
  6689.  
  6690.    fname1  db      'OLDFILE.DAT',0 ; input filename
  6691.    fname2  db      'NEWFILE.DAT',0 ; output filename
  6692.  
  6693.    fhandl1 dw      0               ; input file handle
  6694.    fhandl2 dw      0               ; output file handle
  6695.  
  6696.    faction dw      0               ; receives DosOpen action
  6697.  
  6698.    rlen    dw      0               ; length from DosRead
  6699.    wlen    dw      0               ; length from DosWrite
  6700.  
  6701.    buffer  db      recsize dup (?) ; buffer for file I/O
  6702.  
  6703.            .
  6704.            .
  6705.            .
  6706.                                    ; open input file...
  6707.            push    ds              ; input filename
  6708.            push    offset DGROUP:fname1
  6709.            push    ds              ; receives handle
  6710.            push    offset DGROUP:fhandl1
  6711.            push    ds              ; receives DosOpen action
  6712.            push    offset DGROUP:faction
  6713.            push    0               ; file size (ignored)
  6714.            push    0
  6715.            push    0               ; file attribute (ignored)
  6716.            push    1               ; flag: fail if file
  6717.                                    ;       doesn't exist
  6718.            push    40h             ; mode: deny-none,
  6719.                                    ;       access read-only
  6720.            push    0               ; reserved DWORD 0
  6721.            push    0
  6722.            call    DosOpen         ; transfer to OS/2
  6723.  
  6724.            or      ax,ax           ; was open successful?
  6725.            jnz     error           ; jump if function failed
  6726.            .
  6727.            .
  6728.            .                       ; create output file...
  6729.            push    ds              ; output filename
  6730.            push    offset DGROUP:fname2
  6731.            push    ds              ; receives handle
  6732.            push    offset DGROUP:fhandl2
  6733.            push    ds              ; receives DosOpen action
  6734.            push    offset DGROUP:faction
  6735.            push    0               ; initial file size = 0
  6736.            push    0
  6737.            push    0               ; attribute = normal
  6738.            push    12              ; flag: create or replace
  6739.            push    41h             ; mode: deny-none,
  6740.                                    ;       access write-only
  6741.            push    0               ; reserved DWORD 0
  6742.            push    0
  6743.            call    DosOpen         ; transfer to OS/2
  6744.  
  6745.            or      ax,ax           ; was create successful?
  6746.            jnz     error           ; jump if function failed
  6747.  
  6748.    next:                           ; read next record from
  6749.                                    ; input file...
  6750.  
  6751.            push    fhandl1         ; input file handle
  6752.            push    ds              ; buffer address
  6753.            push    offset DGROUP:buffer
  6754.            push    recsize         ; length to read
  6755.            push    ds              ; receives actual length
  6756.            push    offset DGROUP:rlen
  6757.            call    DosRead         ; transfer to OS/2
  6758.  
  6759.            or      ax,ax           ; was read successful?
  6760.            jnz     error           ; jump if function failed
  6761.  
  6762.            cmp     rlen,0          ; read length = 0?
  6763.            je      done            ; jump if end of file
  6764.  
  6765.            .                       ; other processing of record
  6766.            .                       ; performed here...
  6767.            .
  6768.                                    ; write processed record
  6769.                                    ; to output file...
  6770.  
  6771.            push    fhandl2         ; output file handle
  6772.            push    ds              ; buffer address
  6773.            push    offset DGROUP:buffer
  6774.            push    rlen            ; write length = read length
  6775.            push    ds              ; receives actual length
  6776.            push    offset DGROUP:wlen
  6777.            call    DosWrite        ; transfer to OS/2
  6778.  
  6779.            or      ax,ax           ; was write successful?
  6780.            jnz     error           ; jump if function failed
  6781.  
  6782.            mov     ax,rlen         ; was write complete?
  6783.            cmp     ax,wlen
  6784.            jne     error           ; jump if disk full
  6785.  
  6786.            jmp     next            ; process another record
  6787.  
  6788.    done:                           ; end of file reached...
  6789.  
  6790.            push    fhandl1         ; close input file
  6791.            call    DosClose        ; transfer to OS/2
  6792.  
  6793.            push    fhandl2         ; close output file
  6794.            call    DosClose        ; transfer to OS/2
  6795.  
  6796.                                    ; now terminate process...
  6797.            push    1               ; terminate all threads
  6798.            push    0               ; return code = 0 (success)
  6799.            call    DosExit         ; transfer to OS/2
  6800.    ──────────────────────────────────────────────────────────────────────────
  6801.  
  6802.    Figure 8-8.  Skeleton code for an OS/2 assembly language program that
  6803.    performs synchronous, sequential file access. In this simple example,
  6804.    partial records at end of file do not receive any special treatment, and
  6805.    the error status returned by DosClose is ignored.
  6806.  
  6807.    DosRead and DosWrite perform synchronous reads and writes. Both functions
  6808.    require a handle, the address of the data to be written or the buffer to
  6809.    receive the data to be read, a length, and the address of a variable. When
  6810.    the functions return, the variable contains the number of bytes actually
  6811.    transferred. In the case of files, the number of bytes transferred might
  6812.    be less than the number requested for two reasons: The data was being
  6813.    written to disk, and the disk was full; or the data was being read from
  6814.    disk, and the end of the file was reached. Neither of these events is
  6815.    considered to be an error; that is, zero is returned in AX for both.
  6816.  
  6817.    The functions DosReadAsync and DosWriteAsync provide asynchronous reads
  6818.    and writes. These functions require the same parameters as DosRead and
  6819.    DosWrite; in addition, they require addresses for a RAM semaphore and a
  6820.    variable to receive an error code. The program sets the semaphore before
  6821.    calling DosReadAsync or DosWriteAsync; when the requested operation is
  6822.    complete, the semaphore is cleared, and an error code and number of bytes
  6823.    transferred are returned in variables. (Chapter 13 provides more
  6824.    information about semaphores.)
  6825.  
  6826.    OS/2's support for threads provided a simple means of implementing
  6827.    DosReadAsync and DosWriteAsync. These functions merely create a new thread
  6828.    on behalf of the calling process that executes──carrying out the I/O,
  6829.    clearing the semaphore, and then destroying itself──while the original
  6830.    thread continues its execution. You can generally get better performance
  6831.    in your application by using multiple threads explicitly, rather than
  6832.    letting them be created and destroyed in this hidden manner.
  6833.  
  6834.    DosChgFilePtr allows a process to set the file pointer associated with a
  6835.    handle to an arbitrary offset. The program supplies a code that indicates
  6836.    the "method" in which the offset is specified: relative to the start of
  6837.    file (method 0), to the current file pointer (method 1), or to the end of
  6838.    file (method 2). When methods 1 and 2 are used, the offset can be either
  6839.    positive or negative. In any case, the function returns the resulting
  6840.    absolute offset from the start of file.
  6841.  
  6842.    There is little distinction between sequential and random access under
  6843.    OS/2. When a file is opened or created, its file pointer is initially set
  6844.    to zero. If a process simply issues reads or writes without ever calling
  6845.    DosChgFilePtr, then its access to the file is sequential. If a process
  6846.    requires random access, it need only call DosChgFilePtr before each read
  6847.    or write to position the file pointer appropriately. Naturally, these
  6848.    techniques can be used together; a process can seek to a specific record
  6849.    and then process sequential records by consecutive read or write
  6850.    operations.
  6851.  
  6852.    Note the following special uses of DosChgFilePtr: To obtain the current
  6853.    position of the file pointer without modifying it, call the function with
  6854.    method 1 and an offset of zero; to find the file size, call DosChgFilePtr
  6855.    with method 2 and an offset of zero.
  6856.  
  6857.    ──────────────────────────────────────────────────────────────────────────
  6858.    WARNING:
  6859.      When multiple threads use the same file handle, they must do so
  6860.      cooperatively because OS/2 does not attempt to serialize requests from
  6861.      different threads or protect one thread from another. For example, if a
  6862.      thread calls DosChgFilePtr followed by DosRead to read a specific
  6863.      record──and another thread's request using the same handle gets serviced
  6864.      in between──the file pointer will be changed, and the first thread will
  6865.      get the wrong data. The order in which requests from different threads
  6866.      are completed depends on the priorities of the threads and on the
  6867.      physical location of the data on the disk as well as on the order of the
  6868.      requests themselves.
  6869.    ──────────────────────────────────────────────────────────────────────────
  6870.  
  6871.  
  6872.  Forcing Disk Updates
  6873.  
  6874.    OS/2's ability to buffer data internally to eliminate unnecessary reads or
  6875.    defer writes improves system throughput. However, some programs must be
  6876.    able to bring a file──on demand──to a consistent state on the physical
  6877.    disk, in case power fails, the system crashes, or the process terminates
  6878.    unexpectedly. Imagine, for example, the potential problems if the index to
  6879.    a database was updated but the corresponding record was never written, or
  6880.    vice versa.
  6881.  
  6882.    The function DosBufReset writes the system's internal buffers associated
  6883.    with a specific file handle to the disk and updates the corresponding
  6884.    entry in the disk directory; the original handle remains open for further
  6885.    use. If DosBufReset is called with a handle of -1, the buffers are flushed
  6886.    and the directory entries updated for all of the process's open files. If
  6887.    any files are on removable media and those media were changed, OS/2
  6888.    prompts the user to mount the necessary disks.
  6889.  
  6890.    The methods used in MS-DOS programming to force a disk update──closing and
  6891.    reopening a handle or duplicating and closing a handle──are inefficient
  6892.    and unnecessary in OS/2.
  6893.  
  6894.  
  6895.  File and Record Locking
  6896.  
  6897.    In a multitasking, network-oriented environment such as OS/2, it is to be
  6898.    expected that the user will (intentionally or unintentionally) attempt to
  6899.    use the same file with several programs at once, or open a file on a
  6900.    network server that is also in use by a program running in another network
  6901.    node. If programs do not defend themselves against the user by proper use
  6902.    of OS/2's sharing and locking facilities, deadlocks or race conditions can
  6903.    occur, and data can be lost or corrupted.
  6904.  
  6905.    You can restrict access by other processes to an entire file by using the
  6906.    proper sharing mode in a DosOpen call. After a process successfully opens
  6907.    a file with a sharing mode of deny-all or deny-write, it can modify the
  6908.    file with impunity. Such a process is not regarded as "friendly" in a
  6909.    multitasking environment, however, because it interferes with other
  6910.    processes' access to the file for a prolonged period.
  6911.  
  6912.    A better approach is to open the file with a sharing mode of deny-none and
  6913.    then to exclude other processes from access to specific file regions only
  6914.    during updates of those regions. The function DosFileLocks allows a
  6915.    process to lock, or request exclusive access to, a file region of
  6916.    arbitrary position and size. Another locked region can be unlocked during
  6917.    the same function call. While a lock is in effect, calls issued by other
  6918.    processes to read, write, or lock the same file region will fail. If a
  6919.    process cannot lock a file region, it can delay and retry the lock
  6920.    operation, or it can display a message so that the user can resolve the
  6921.    contention.
  6922.  
  6923.    The system recognizes locks on file regions on a per-process basis. Thus,
  6924.    child processes do not inherit region locks, although they do inherit
  6925.    overall sharing modes and access rights for a file. Also, a DosFileLocks
  6926.    call does not protect one thread against interference by another using the
  6927.    same handle; such protection must be obtained by other means.
  6928.  
  6929.  
  6930.  Renaming and Deleting Files
  6931.  
  6932.    Use DosMove to rename a file or move it to another directory of the same
  6933.    drive (or to do both at once). The function requires two ASCIIZ pathnames
  6934.    for the old and new locations and names of the file; it returns an error
  6935.    if the file is currently opened by the same or another process, if some
  6936.    element of the old or new directory path does not exist, or if a file by
  6937.    the new name already exists in the destination directory. In contrast to
  6938.    the user-level RENAME command, DosMove works on one file at a time and
  6939.    does not accept wildcard characters in filenames.
  6940.  
  6941.    The function DosDelete removes a file from the disk directory. It fails if
  6942.    the file is read-only or is currently opened by the same or another
  6943.    process. Unlike the DEL and ERASE user commands supported by CMD.EXE and
  6944.    COMMAND.COM, DosDelete does not tolerate pathnames that contain wildcards
  6945.    and only deletes one file at a time.
  6946.  
  6947.    When you delete a file in FAT file systems, its directory entry is marked
  6948.    as available by a change in the first byte of the entry (to a reserved
  6949.    value). The disk sectors previously allocated to the file are released.
  6950.    The remainder of the directory entry and the actual contents of the disk
  6951.    sectors are not altered, making it possible for "un-erase" programs to
  6952.    reclaim the file under some circumstances. A program that requires a
  6953.    higher degree of security should overwrite the entire file with zeros or
  6954.    other data before it deletes the file.
  6955.  
  6956.  
  6957.  Sample Programs: DUMP.ASM and DUMP.C
  6958.  
  6959.    The listings DUMP.C (Figure 8-9) and DUMP.ASM (Figure 8-10) are the
  6960.    source code for DUMP.EXE, a utility program that displays the binary
  6961.    contents of a file in hex and ASCII. These listings illustrate many of the
  6962.    basic file management techniques you will need in your own application
  6963.    programs. The two programs are symmetric and produce identical output (an
  6964.    example of which is shown in Figure 8-14 on p. 163).
  6965.  
  6966.    ──────────────────────────────────────────────────────────────────────────
  6967.    /*
  6968.            DUMP.C
  6969.  
  6970.            Displays the binary contents of a file in hex and ASCII
  6971.            on the standard output device. Demonstrates direct
  6972.            calls to OS/2 API from a C program.
  6973.  
  6974.            Compile with:  C> cl dump.c
  6975.  
  6976.            Usage is:  C> dump pathname.ext [>destination]
  6977.  
  6978.            Copyright (C) 1987 Ray Duncan
  6979.    */
  6980.  
  6981.    #include <stdio.h>
  6982.  
  6983.    #define RECSIZE 16                  /* size of file records */
  6984.  
  6985.    #define API unsigned extern far pascal
  6986.  
  6987.    API DosClose(unsigned);             /* function prototypes */
  6988.  
  6989.    API DosOpen(char far *, unsigned far *, unsigned far *, unsigned long,
  6990.                unsigned, unsigned, unsigned, unsigned long);
  6991.  
  6992.    API DosRead(unsigned, void far *, unsigned, unsigned far *);
  6993.  
  6994.    main(int argc, char *argv[])
  6995.    {
  6996.        char file_buf[RECSIZE];         /* data block from file */
  6997.        unsigned long foffset = 0L;     /* file offset in bytes */
  6998.        unsigned handle;                /* DosOpen variable */
  6999.        unsigned action, length;        /* DosRead variables */
  7000.        unsigned flag = 0x01;           /* fail if file not found */
  7001.        unsigned mode = 0x40;           /* read-only, deny-none */
  7002.  
  7003.        if(argc < 2)                    /* check command tail */
  7004.        {
  7005.            fprintf(stderr, "\ndump: missing filename\n");
  7006.            exit(1);
  7007.        }
  7008.                                        /* open file or exit */
  7009.        if(DosOpen(argv[1], &handle, &action, 0L, 0, flag, mode, 0L))
  7010.        {
  7011.            fprintf(stderr, "\ndump: can't find file %s\n", argv[1]);
  7012.            exit(1);
  7013.        }
  7014.                                        /* read and dump records */
  7015.        while((DosRead(handle, file_buf, RECSIZE, &length) == 0)
  7016.               && (length != 0))
  7017.        {
  7018.            dump_rec(file_buf, foffset, length);
  7019.            foffset += RECSIZE;
  7020.        }
  7021.        printf("\n");                   /* extra blank line */
  7022.        DosClose(handle);               /* close the input file */
  7023.        exit(0);                        /* return success code */
  7024.    }
  7025.  
  7026.    /*
  7027.        Display record (16 bytes) in hex and ASCII on standard output.
  7028.    */
  7029.    dump_rec(char *buffer, long foffset, int length)
  7030.    {
  7031.        int i;                          /* index to current record */
  7032.  
  7033.        if(foffset % 128 == 0)          /* maybe print heading */
  7034.            printf("\n\n       0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F")
  7035.  
  7036.        printf("\n%04lX ", foffset);    /* file offset */
  7037.  
  7038.        for(i = 0; i < length; i++)     /* print hex equivalent of each byte */
  7039.            printf(" %02X", (unsigned char) buffer[i]);
  7040.  
  7041.        if(length != 16)                /* space over if last record */
  7042.            for(i=0; i<(16-length); i++) printf("   ");
  7043.        printf("  ");
  7044.        for(i = 0; i < length; i++)     /* print ASCII equiv. of bytes */
  7045.        {
  7046.            if(buffer[i] < 32 || buffer[i] > 126) putchar('.');
  7047.            else putchar(buffer[i]);
  7048.        }
  7049.    }
  7050.    ──────────────────────────────────────────────────────────────────────────
  7051.  
  7052.    Figure 8-9.  DUMP.C, the C source code for DUMP.EXE.
  7053.  
  7054.    ──────────────────────────────────────────────────────────────────────────
  7055.            title   DUMP -- Display File Contents
  7056.            page    55,132
  7057.            .286
  7058.  
  7059.    ;
  7060.    ; DUMP.ASM
  7061.    ;
  7062.    ; Displays the binary contents of a file in hex and ASCII on the
  7063.    ; standard output device.
  7064.    ;
  7065.    ; Assemble with:  C> masm dump.asm;
  7066.    ; Link with:  C> link dump,,,os2,dump
  7067.    ;
  7068.    ; Usage is:  C> dump pathname.ext [>destination]
  7069.    ;
  7070.    ; Copyright (C) 1988 Ray Duncan
  7071.    ;
  7072.  
  7073.    cr      equ     0dh                     ; ASCII carriage return
  7074.    lf      equ     0ah                     ; ASCII line feed
  7075.    blank   equ     20h                     ; ASCII space code
  7076.  
  7077.    blksize equ     16                      ; size of input file records
  7078.  
  7079.    stdout  equ     1                       ; standard output handle
  7080.    stderr  equ     2                       ; standard error handle
  7081.  
  7082.            extrn   DosOpen:far
  7083.            extrn   DosRead:far
  7084.            extrn   DosWrite:far
  7085.            extrn   DosClose:far
  7086.            extrn   DosExit:far
  7087.            extrn   argc:near               ; returns argument count
  7088.            extrn   argv:near               ; returns argument pointer
  7089.  
  7090.    DGROUP  group   _DATA
  7091.  
  7092.    _DATA   segment word public 'DATA'
  7093.  
  7094.    fname   db      64 dup (0)              ; name of input file
  7095.  
  7096.    fhandle dw      0                       ; input file handle
  7097.  
  7098.    faction dw      0                       ; action from DosOpen
  7099.  
  7100.    fptr    dw      0                       ; relative file address
  7101.  
  7102.    rlen    dw      0                       ; actual number of bytes
  7103.                                            ; read by DosRead
  7104.  
  7105.    wlen    dw      0                       ; actual number of bytes
  7106.                                            ; written by DosWrite
  7107.  
  7108.    output  db      'nnnn',blank,blank      ; output format area
  7109.    outputa db      16 dup ('nn',blank)
  7110.            db      blank
  7111.    outputb db      16 dup (blank),cr,lf
  7112.    output_len equ $-output
  7113.  
  7114.    hdg     db      cr,lf
  7115.            db      7 dup (blank)
  7116.            db      '0  1  2  3  4  5  6  7  '
  7117.            db      '8  9  A  B  C  D  E  F',cr,lf
  7118.    hdg_len equ $-hdg
  7119.  
  7120.    fbuff   db      blksize dup (?)         ; data from file
  7121.  
  7122.    msg1    db      cr,lf
  7123.            db      'dump: file not found'
  7124.            db      cr,lf
  7125.    msg1_len equ $-msg1
  7126.  
  7127.    msg2    db      cr,lf
  7128.            db      'dump: missing filename'
  7129.            db      cr,lf
  7130.    msg2_len equ $-msg2
  7131.    msg3    db      cr,lf
  7132.            db      'dump: empty file'
  7133.            db      cr,lf
  7134.    msg3_len equ $-msg3
  7135.  
  7136.    _DATA   ends
  7137.  
  7138.    _TEXT   segment word public 'CODE'
  7139.  
  7140.            assume  cs:_TEXT,ds:DGROUP
  7141.  
  7142.    dump    proc    far                     ; entry point from OS/2
  7143.  
  7144.            push    ds                      ; make DGROUP addressable
  7145.            pop     es                      ; via ES
  7146.  
  7147.            call    argc                    ; is filename present
  7148.            cmp     ax,2                    ; in command tail?
  7149.            je      dump1                   ; yes, proceed
  7150.  
  7151.            mov     dx,offset msg2          ; missing or illegal filespec,
  7152.            mov     cx,msg2_len
  7153.            jmp     dump9                   ; display error message and exit
  7154.  
  7155.    dump1:                                  ; copy filename from command
  7156.                                            ; tail to local buffer
  7157.  
  7158.            mov     ax,1                    ; get pointer to command tail
  7159.            call    argv                    ; argument in ES:BX
  7160.            mov     cx,ax                   ; CX = filename length
  7161.            mov     di,offset fname         ; DS:DI = local buffer
  7162.  
  7163.    dump2:  mov     al,es:[bx]              ; copy filename byte by byte
  7164.            mov     [di],al
  7165.            inc     bx
  7166.            inc     di
  7167.            loop    dump2
  7168.  
  7169.            push    ds                      ; restore ES = DGROUP
  7170.            pop     es
  7171.  
  7172.    dump4:                                  ; try to open file...
  7173.            push    ds                      ; address of filename
  7174.            push    offset fname
  7175.            push    ds                      ; receives file handle
  7176.            push    offset fhandle
  7177.            push    ds
  7178.            push    offset faction          ; receives DosOpen action
  7179.            push    0                       ; file size (ignored)
  7180.            push    0
  7181.            push    0                       ; file attribute (ignored)
  7182.            push    1                       ; OpenFlag:
  7183.                                            ; fail if file doesn't exist
  7184.            push    40h                     ; OpenMode:
  7185.                                            ; deny-none, access = read-only
  7186.            push    0                       ; reserved DWORD 0
  7187.            push    0
  7188.            call    DosOpen                 ; transfer to OS/2
  7189.            or      ax,ax                   ; was open successful?
  7190.            jz      dump5                   ; yes, proceed
  7191.  
  7192.            mov     dx,offset msg1          ; open failed, display
  7193.            mov     cx,msg1_len             ; error message and exit
  7194.            jmp     dump9
  7195.  
  7196.    dump5:  call    rdblk                   ; initialize file buffer
  7197.            cmp     rlen,0                  ; anything read?
  7198.            jne     dump6                   ; jump, got some data
  7199.            cmp     fptr,0                  ; no data, was this first read?
  7200.            jne     dump8                   ; no, end of file reached
  7201.  
  7202.            mov     dx,offset msg3          ; empty file, print error
  7203.            mov     cx,msg3_len             ; message and exit
  7204.            jmp     dump9
  7205.  
  7206.    dump6:  test    fptr,07fh               ; time for a heading?
  7207.            jnz     dump7                   ; no, jump
  7208.  
  7209.                                            ; write heading...
  7210.            push    stdout                  ; standard output handle
  7211.            push    ds                      ; address of heading
  7212.            push    offset hdg
  7213.            push    hdg_len                 ; length of heading
  7214.            push    ds                      ; receives bytes written
  7215.            push    offset wlen
  7216.            call    DosWrite
  7217.  
  7218.    dump7:  call    cnvblk                  ; convert one block of
  7219.                                            ; binary data to ASCII
  7220.                                            ; write formatted output...
  7221.            push    stdout                  ; standard output handle
  7222.            push    ds                      ; address of output
  7223.            push    offset output
  7224.            push    output_len              ; length of output
  7225.            push    ds                      ; receives bytes written
  7226.            push    offset wlen
  7227.            call    DosWrite
  7228.  
  7229.            jmp     dump5                   ; get more data
  7230.  
  7231.    dump8:                                  ; end of file reached...
  7232.            push    fhandle                 ; close input file
  7233.            call    DosClose                ; transfer to OS/2
  7234.  
  7235.                                            ; final exit to OS/2...
  7236.            push    1                       ; terminate all threads
  7237.            push    0                       ; return code = 0 (success)
  7238.            call    DosExit                 ; transfer to OS/2
  7239.  
  7240.    dump9:                                  ; print error message on
  7241.                                            ; standard error device
  7242.            push    stderr
  7243.            push    ds                      ; address of message
  7244.            push    dx
  7245.            push    cx                      ; length of message
  7246.            push    ds                      ; receives bytes written
  7247.            push    offset wlen
  7248.            call    DosWrite                ; transfer to OS/2
  7249.  
  7250.                                            ; final exit to OS/2...
  7251.            push    1                       ; terminate all threads
  7252.            push    1                       ; return code = 1 (error)
  7253.            call    DosExit                 ; transfer to OS/2
  7254.  
  7255.    dump    endp
  7256.  
  7257.    ; RDBLK:        Read block of data from input file
  7258.    ;
  7259.    ; Call with:    nothing
  7260.    ; Returns:      AX = error code (0 = no error)
  7261.    ; Uses:         nothing
  7262.  
  7263.    rdblk   proc    near
  7264.            push    fhandle                 ; input file handle
  7265.            push    ds                      ; buffer address
  7266.            push    offset fbuff
  7267.            push    blksize                 ; buffer length
  7268.            push    ds                      ; receives bytes read
  7269.            push    offset rlen
  7270.            call    DosRead                 ; transfer to OS/2
  7271.            ret                             ; back to caller
  7272.  
  7273.    rdblk   endp
  7274.  
  7275.    ; CNVBLK:       Format one binary record for output
  7276.    ;
  7277.    ; Call with:    nothing
  7278.    ; Returns:      nothing
  7279.    ; Uses:         AX, BX, CX, DX, DI
  7280.  
  7281.    cnvblk  proc    near
  7282.  
  7283.            mov     di,offset output        ; clear output format
  7284.            mov     cx,output_len-2         ; area to blanks
  7285.            mov     al,blank
  7286.            rep stosb
  7287.  
  7288.            mov     di,offset output        ; convert current file
  7289.            mov     ax,fptr                 ; offset to ASCII
  7290.            call    wtoa
  7291.  
  7292.            xor     bx,bx                   ; point to start of data
  7293.  
  7294.    cb1:    mov     al,[fbuff+bx]           ; get next byte of data
  7295.                                            ; from input file
  7296.  
  7297.            lea     di,[bx+outputb]         ; calculate output address
  7298.                                            ; for ASCII equivalent
  7299.            mov     byte ptr [di],'.'       ; if control character,
  7300.            cmp     al,blank                ; substitute a period
  7301.            jb      cb2                     ; jump, not alphanumeric
  7302.            cmp     al,7eh
  7303.            ja      cb2                     ; jump, not alphanumeric
  7304.            mov     [di],al                 ; store ASCII character
  7305.    cb2:                                    ; now convert byte to hex
  7306.            mov     di,bx                   ; calculate output address
  7307.            imul    di,di,3                 ; (position*3) + base address
  7308.            add     di,offset outputa
  7309.            call    btoa                    ; convert data byte to hex
  7310.  
  7311.            inc     bx                      ; advance through record
  7312.            cmp     bx,rlen                 ; entire buffer converted?
  7313.            jne     cb1                     ; no, get another byte
  7314.  
  7315.            add     fptr,blksize            ; update file offset
  7316.  
  7317.            ret                             ; back to caller
  7318.  
  7319.    cnvblk  endp
  7320.  
  7321.  
  7322.    ; WTOA:         Convert word to hex ASCII
  7323.    ;
  7324.    ; Call with:    AX    = data to convert
  7325.    ;               ES:DI = storage address
  7326.    ; Returns:      nothing
  7327.    ; Uses:         AX, CL, DI
  7328.  
  7329.    wtoa    proc    near
  7330.  
  7331.            push    ax                      ; save original value
  7332.            mov     al,ah
  7333.            call    btoa                    ; convert upper byte
  7334.  
  7335.            pop     ax                      ; restore original value
  7336.            call    btoa                    ; convert lower byte
  7337.  
  7338.            ret                             ; back to caller
  7339.  
  7340.    wtoa    endp
  7341.  
  7342.  
  7343.    ; BTOA:         Convert byte to hex ASCII
  7344.    ;
  7345.    ; Call with:    AL    = data to convert
  7346.    ;               ES:DI = storage address
  7347.    ; Returns:      nothing
  7348.    ; Uses:         AX, CL, DI
  7349.  
  7350.    btoa    proc    near
  7351.            sub     ah,ah                   ; clear upper byte
  7352.  
  7353.            mov     cl,16                   ; divide by 16
  7354.            div     cl
  7355.  
  7356.            call    ascii                   ; convert quotient
  7357.            stosb                           ; store ASCII character
  7358.  
  7359.            mov     al,ah
  7360.            call    ascii                   ; convert remainder
  7361.            stosb                           ; store ASCII character
  7362.  
  7363.            ret                             ; back to caller
  7364.  
  7365.    btoa    endp
  7366.  
  7367.    ; ASCII:        Convert nibble to hex ASCII
  7368.    ;
  7369.    ; Call with:    AL    = data to convert in low 4 bits
  7370.    ; Returns:      AL    = ASCII character
  7371.    ; Uses:         nothing
  7372.  
  7373.    ascii   proc    near
  7374.  
  7375.            add     al,'0'                  ; add base ASCII value
  7376.            cmp     al,'9'                  ; is it in range 0-9?
  7377.            jle     ascii2                  ; jump if it is
  7378.  
  7379.            add     al,'A'-'9'-1            ; no, adjust for range A-F
  7380.  
  7381.    ascii2: ret                             ; return ASCII character in AL
  7382.  
  7383.    ascii   endp
  7384.  
  7385.    _TEXT   ends
  7386.  
  7387.            end     dump
  7388.    ──────────────────────────────────────────────────────────────────────────
  7389.  
  7390.    Figure 8-10.  DUMP.ASM, the MASM source code for DUMP.EXE. Figures 8-11
  7391.    and 8-12 provide the source code for the subroutines ARGV and ARGC.
  7392.  
  7393.    DUMP parses the pathname for the file to be displayed from the command
  7394.    tail at the end of the environment. It sends the formatted dump to the
  7395.    standard output device; you can redirect the output to a file, another
  7396.    character device, or another process through a pipe. Error messages are
  7397.    sent to the standard error device so that they will not be affected by
  7398.    redirection of the standard output.
  7399.  
  7400.    The C version of DUMP demonstrates calls to the OS/2 API from a high level
  7401.    language. Calling OS/2 functions directly for file management, instead of
  7402.    using the C runtime library, makes the program smaller and faster (but
  7403.    less portable).
  7404.  
  7405.    The MASM version of DUMP works in much the same way as the C version but
  7406.    yields an EXE file that is only one-fifth as large. DUMP.ASM uses two
  7407.    external subroutines, ARGC.ASM (Figure 8-11) and ARGV.ASM (Figure 8-12),
  7408.    which you will find helpful in just about any MASM program and which will
  7409.    be used again several times in this book. ARGC returns the number of
  7410.    command line arguments, and ARGV returns the address and length of a
  7411.    particular command line argument.
  7412.  
  7413.    ──────────────────────────────────────────────────────────────────────────
  7414.            title   ARGC -- Return Argument Count
  7415.            page    55,132
  7416.            .286
  7417.  
  7418.    ;
  7419.    ; ARGC.ASM
  7420.    ;
  7421.    ; Return count of command line arguments. Treats blanks and
  7422.    ; tabs as whitespace.
  7423.    ;
  7424.    ; Assemble with:  C> masm argc.asm;
  7425.    ;
  7426.    ; Call with:    N/A
  7427.    ;
  7428.    ; Returns:      AX    = argument count (always >= 1)
  7429.    ;
  7430.    ; Uses:         nothing (other registers preserved)
  7431.    ;
  7432.    ; Copyright (C) 1987 Ray Duncan
  7433.    ;
  7434.  
  7435.    tab     equ     09h             ; ASCII tab
  7436.    blank   equ     20h             ; ASCII space character
  7437.  
  7438.            extrn   DosGetEnv:far
  7439.  
  7440.    _TEXT   segment word public 'CODE'
  7441.  
  7442.            assume  cs:_TEXT
  7443.            public  argc            ; make ARGC available to Linker
  7444.  
  7445.                                    ; local variables
  7446.    envseg  equ     [bp-2]          ; environment segment
  7447.    cmdoffs equ     [bp-4]          ; command line offset
  7448.  
  7449.    argc    proc    near
  7450.  
  7451.            enter   4,0             ; make room for local variables
  7452.  
  7453.            push    es              ; save original ES, BX, and CX
  7454.            push    bx
  7455.            push    cx
  7456.  
  7457.            push    ss              ; get selector for environment
  7458.            lea     ax,envseg       ; and offset of command line
  7459.            push    ax
  7460.            push    ss
  7461.            lea     ax,cmdoffs
  7462.            push    ax
  7463.            call    DosGetEnv       ; transfer to OS/2
  7464.            or      ax,ax           ; check operation status
  7465.            mov     ax,1            ; force argc >= 1
  7466.            jnz     argc3           ; inexplicable failure
  7467.  
  7468.            mov     es,envseg       ; set ES:BX = command line
  7469.            mov     bx,cmdoffs
  7470.  
  7471.    argc0:  inc     bx              ; ignore useless first field
  7472.            cmp     byte ptr es:[bx],0
  7473.            jne     argc0
  7474.  
  7475.    argc1:  mov     cx,-1           ; set flag = outside argument
  7476.  
  7477.    argc2:  inc     bx              ; point to next character
  7478.            cmp     byte ptr es:[bx],0
  7479.            je      argc3           ; exit if null byte
  7480.            cmp     byte ptr es:[bx],blank
  7481.            je      argc1           ; outside argument if ASCII blank
  7482.            cmp     byte ptr es:[bx],tab
  7483.            je      argc1           ; outside argument if ASCII tab
  7484.  
  7485.                                    ; otherwise not blank or tab,
  7486.            jcxz    argc2           ; jump if already inside argument
  7487.  
  7488.            inc     ax              ; else found argument, count it
  7489.            not     cx              ; set flag = inside argument
  7490.            jmp     argc2           ; and look at next character
  7491.    argc3:  pop     cx              ; restore original BX, CX, ES
  7492.            pop     bx
  7493.            pop     es
  7494.            leave                   ; discard local variables
  7495.            ret                     ; return AX = argument count
  7496.  
  7497.    argc    endp
  7498.  
  7499.    _TEXT   ends
  7500.  
  7501.            end
  7502.    ──────────────────────────────────────────────────────────────────────────
  7503.  
  7504.    Figure 8-11.  ARGC.ASM, the MASM source code for the ARGC routine called
  7505.    by the DUMP utility. This procedure returns the number of command line
  7506.    arguments.
  7507.  
  7508.    ──────────────────────────────────────────────────────────────────────────
  7509.            title   ARGV -- Return Argument Pointer
  7510.            page    55,132
  7511.            .286
  7512.  
  7513.    ;
  7514.    ; ARGV.ASM
  7515.    ;
  7516.    ; Return address and length of specified command line argument
  7517.    ; or fully qualified program name. Treats blanks and tabs as
  7518.    ; whitespace.
  7519.    ;
  7520.    ; Assemble with:  C> masm argv.asm;
  7521.    ;
  7522.    ; Call with:    AX    = argument number (0 based)
  7523.    ;
  7524.    ; Returns:      ES:BX = argument address
  7525.    ;               AX    = argument length (0 = no argument)
  7526.    ;
  7527.    ; Uses:         nothing (other registers preserved)
  7528.    ;
  7529.    ; Note: if called with AX = 0 (argv[0]), returns ES:BX
  7530.    ; pointing to fully qualified program name in environment
  7531.    ; block and AX = length.
  7532.    ;
  7533.    ; Copyright (C) 1987 Ray Duncan
  7534.    ;
  7535.    tab     equ     09h             ; ASCII tab
  7536.    blank   equ     20h             ; ASCII space character
  7537.  
  7538.            extrn   DosGetEnv:far
  7539.  
  7540.    _TEXT   segment word public 'CODE'
  7541.  
  7542.            assume  cs:_TEXT
  7543.  
  7544.            public  argv            ; make ARGV available to Linker
  7545.  
  7546.                                    ; local variables...
  7547.    envseg  equ     [bp-2]          ; environment segment
  7548.    cmdoffs equ     [bp-4]          ; command line offset
  7549.  
  7550.    argv    proc    near
  7551.  
  7552.            enter   4,0             ; make room for local variables
  7553.            push    cx              ; save original CX and DI
  7554.            push    di
  7555.  
  7556.            push    ax              ; save argument number
  7557.  
  7558.            push    ss              ; get selector for environment
  7559.            lea     ax,envseg       ; and offset of command line
  7560.            push    ax
  7561.            push    ss
  7562.            lea     ax,cmdoffs
  7563.            push    ax
  7564.            call    DosGetEnv       ; transfer to OS/2
  7565.            or      ax,ax           ; test operation status
  7566.            pop     ax              ; restore argument number
  7567.            jnz     argv7           ; jump if DosGetEnv failed
  7568.  
  7569.            mov     es,envseg       ; set ES:BX = command line
  7570.            mov     bx,cmdoffs
  7571.  
  7572.            or      ax,ax           ; is requested argument = 0?
  7573.            jz      argv8           ; yes, jump to get program name
  7574.  
  7575.    argv0:  inc     bx              ; scan off first field
  7576.            cmp     byte ptr es:[bx],0
  7577.            jne     argv0
  7578.  
  7579.            xor     ah,ah           ; initialize argument counter
  7580.  
  7581.    argv1:  mov     cx,-1           ; set flag = outside argument
  7582.    argv2:  inc     bx              ; point to next character
  7583.            cmp     byte ptr es:[bx],0
  7584.            je      argv7           ; exit if null byte
  7585.            cmp     byte ptr es:[bx],blank
  7586.            je      argv1           ; outside argument if ASCII blank
  7587.            cmp     byte ptr es:[bx],tab
  7588.            je      argv1           ; outside argument if ASCII tab
  7589.  
  7590.                                    ; if not blank or tab...
  7591.            jcxz    argv2           ; jump if already inside argument
  7592.  
  7593.            inc     ah              ; else count arguments found
  7594.            cmp     ah,al           ; is this the one we're looking for?
  7595.            je      argv4           ; yes, go find its length
  7596.            not     cx              ; no, set flag = inside argument
  7597.            jmp     argv2           ; and look at next character
  7598.  
  7599.    argv4:                          ; found desired argument, now
  7600.                                    ; determine its length...
  7601.            mov     ax,bx           ; save parameter starting address
  7602.  
  7603.    argv5:  inc     bx              ; point to next character
  7604.            cmp     byte ptr es:[bx],0
  7605.            je      argv6           ; found end if null byte
  7606.            cmp     byte ptr es:[bx],blank
  7607.            je      argv6           ; found end if ASCII blank
  7608.            cmp     byte ptr es:[bx],tab
  7609.            jne     argv5           ; found end if ASCII tab
  7610.  
  7611.    argv6:  xchg    bx,ax           ; set ES:BX = argument address
  7612.            sub     ax,bx           ; and AX = argument length
  7613.            jmp     argvx           ; return to caller
  7614.  
  7615.    argv7:  xor     ax,ax           ; set AX = 0, argument not found
  7616.            jmp     argvx           ; return to caller
  7617.  
  7618.    argv8:                          ; special handling for argv = 0
  7619.            xor     di,di           ; find the program name by
  7620.            xor     al,al           ; first skipping over all the
  7621.            mov     cx,-1           ; environment variables...
  7622.            cld
  7623.    argv9:  repne scasb             ; scan for double null (can't use
  7624.            scasb                   ; SCASW since might be odd address)
  7625.            jne     argv9           ; loop if it was a single null
  7626.            mov     bx,di           ; save program name address
  7627.            mov     cx,-1           ; now find its length...
  7628.            repne scasb             ; scan for another null byte
  7629.            not     cx              ; convert CX to length
  7630.            dec     cx
  7631.            mov     ax,cx           ; return length in AX
  7632.  
  7633.    argvx:                          ; common exit point
  7634.            pop     di              ; restore original CX and DI
  7635.            pop     cx
  7636.            leave                   ; discard stack frame
  7637.            ret                     ; return to caller
  7638.  
  7639.    argv    endp
  7640.  
  7641.    _TEXT   ends
  7642.  
  7643.            end
  7644.    ──────────────────────────────────────────────────────────────────────────
  7645.  
  7646.    Figure 8-12.  ARGV.ASM, the MASM source code for the ARGV routine called
  7647.    by the DUMP utility. This procedure returns the address and length of a
  7648.    specific command line argument.
  7649.  
  7650.    To compile DUMP.C into the relocatable object module DUMP.OBJ and then
  7651.    link DUMP.OBJ and the DUMP.DEF file (Figure 8-13) into the executable
  7652.    module DUMP.EXE, use the following command:
  7653.  
  7654.    [C:\] CL DUMP.C  <Enter>
  7655.  
  7656.    To assemble and link the components of the MASM version of DUMP, collect
  7657.    the source files DUMP.ASM, ARGC.ASM, and ARGV.ASM and the module
  7658.    definition file DUMP.DEF (Figure 8-13). Then enter the following sequence
  7659.    of commands:
  7660.  
  7661.    [C:\] MASM DUMP.ASM;  <Enter>
  7662.    [C:\] MASM ARGC.ASM;  <Enter>
  7663.    [C:\] MASM ARGV.ASM;  <Enter>
  7664.    [C:\] LINK DUMP+ARGC+ARGV,,,OS2,DUMP  <Enter>
  7665.  
  7666.    ──────────────────────────────────────────────────────────────────────────
  7667.    NAME DUMP WINDOWCOMPAT
  7668.    PROTMODE
  7669.    STACKSIZE 4096
  7670.    ──────────────────────────────────────────────────────────────────────────
  7671.  
  7672.    Figure 8-13.  DUMP.DEF, the module definition file for DUMP.EXE.
  7673.  
  7674.    The DUMP program uses only those API functions which have counterparts in
  7675.    MS-DOS, so DUMP is eligible to be converted to a Family application that
  7676.    can run in both real mode and protected mode. To make this conversion,
  7677.    remove the PROTMODE statement from the DUMP.DEF file, rebuild DUMP.EXE,
  7678.    and then bind the program with the following command:
  7679.  
  7680.    [C:\] BIND DUMP.EXE OS2.LIB API.LIB <Enter>
  7681.  
  7682.    The resulting DUMP.EXE file can be executed in either real mode or
  7683.    protected mode on an 80286- or 80386-based machine. Because the program
  7684.    uses 80286-specific instructions (such as PUSH "immediate," ENTER, and
  7685.    LEAVE), you cannot use it on an 8086/88-based machine without modifying
  7686.    the source code.
  7687.  
  7688.    ──────────────────────────────────────────────────────────────────────────
  7689.           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
  7690.    0000  80 06 00 04 44 55 4D 50 40 96 1F 00 00 04 43 4F  ....DUMP@.....CO
  7691.    0010  44 45 04 44 41 54 41 06 44 47 52 4F 55 50 05 5F  DE.DATA.DGROUP._
  7692.    0020  44 41 54 41 05 5F 54 45 58 54 10 98 07 00 68 86  DATA._TEXT....h.
  7693.    0030  01 06 02 01 69 98 07 00 48 1F 01 05 03 01 F0 9A  ....i...H.......
  7694.    0040  04 00 04 FF 02 5D 8C 30 00 08 44 4F 53 43 4C 4F  .....].0..DOSCLO
  7695.    0050  53 45 00 07 44 4F 53 45 58 49 54 00 07 44 4F 53  SE..DOSEXIT..DOS
  7696.    0060  4F 50 45 4E 00 07 44 4F 53 52 45 41 44 00 08 44  OPEN..DOSREAD..D
  7697.    0070  4F 53 57 52 49 54 45 00 18 A0 8A 01 01 00 00 1E  OSWRITE.........
  7698.    ──────────────────────────────────────────────────────────────────────────
  7699.  
  7700.    Figure 8-14.  Example output from DUMP.EXE.
  7701.  
  7702.  
  7703.  
  7704.  ────────────────────────────────────────────────────────────────────────────
  7705.  Chapter 9  Volumes and Directories
  7706.  
  7707.    Each file in an OS/2 system is uniquely identified by name and location.
  7708.    The location, in turn, has two components: a path, which identifies the
  7709.    directory where the filename may be found, and the logical drive on which
  7710.    the directory (and hence the file) resides. The drive and directory may be
  7711.    implicit or explicit; during a process's execution, it has a "current
  7712.    drive" and a "current directory" for each drive, all of which are directly
  7713.    controlled by the process.
  7714.  
  7715.    Logical drives are specified by a single letter (A─Z or a─z) followed by a
  7716.    colon. Drive letters are always assigned starting with A; the highest
  7717.    drive identifier in the system depends on its configuration (number of
  7718.    floppy disk drives, fixed drives, RAM disks, CD-ROMs, and so forth). The
  7719.    number of logical drives in a system may well not be the same as the
  7720.    number of physical drives; for example, large fixed disk drives are
  7721.    commonly broken up into two or more logical drives to make them easier to
  7722.    manage and back up.
  7723.  
  7724.    The key aspect of a logical drive is that it contains a self-consistent──
  7725.    and self-sufficient──file system. That is, it contains one or more
  7726.    directories, zero or more complete files, and all the information needed
  7727.    to locate the files and directories and to determine which disk space is
  7728.    free and which is already in use.
  7729.  
  7730.    A drive's directories are simply little lists or catalogs. Each entry in a
  7731.    directory consists of the name, size, starting location, and attributes of
  7732.    a file or another directory that the drive contains as well as the date
  7733.    and time at which the file or directory was last modified. The detailed
  7734.    information about the location of every block of data assigned to a file
  7735.    or directory is kept in a separate control area on the disk (see Chapter
  7736.    10).
  7737.  
  7738.    Each logical drive has a root directory, which is distinct from all the
  7739.    directories created within it. The root directory is always present and
  7740.    has a maximum number of entries; its size is determined when you format
  7741.    the disk and cannot be changed. Subdirectories of the root directory,
  7742.    which may or may not be present on a given disk, can be nested to any
  7743.    level and can grow to any size (until the space on the disk is exhausted).
  7744.    This organizational scheme is known as a hierarchical, or tree, directory
  7745.    structure (Figure 9-1).
  7746.  
  7747.    The user can interactively examine, select, create, or delete directories
  7748.    with the DIR, CHDIR (CD), MKDIR (MD), or RMDIR (RD) commands. Application
  7749.    programs can carry out similar operations using API calls (summarized in
  7750.    Figure 9-2) to do the following:
  7751.  
  7752.    ■  Search for file or directory entries within directories
  7753.  
  7754.    ■  Obtain or change the current drive or directory for a process
  7755.  
  7756.    ■  Create or delete directories
  7757.  
  7758.    ■  Rename directories (other than the root directory)
  7759.  
  7760.    ■  Obtain, add, or change a volume label
  7761.  
  7762.                               ┌────────────┐
  7763.                               │   Drive    │
  7764.                               │ identifier │
  7765.                               └─────┬──────┘
  7766.                                     │
  7767.                             ┌───────┴────────┐
  7768.                             │ Root directory │
  7769.                             │ (Volume label) │
  7770.                             └┬─┬────┬─────┬─┬┘
  7771.        ┌─────────────────────┘ │    │     │ └────────────────────┐
  7772.        │               ┌───────┘    │     └───────┐              │
  7773.    ┌───┴────┐   ┌──────┴────┐   ┌───┴────┐   ┌────┴──────┐   ┌───┴────┐
  7774.    │ File A │   │ Directory │   │ File B │   │ Directory │   │ File C │
  7775.    └────────┘   └─┬────────┬┘   └────────┘   └┬────────┬─┘   └────────┘
  7776.               ┌───┘        │                  │        └──┐
  7777.        ┌──────┴────┐   ┌───┴────┐        ┌────┴───┐   ┌───┴────┐
  7778.        │ Directory │   │ File D │        │ File E │   │ File F │
  7779.        └───────────┘   └────────┘        └────────┘   └────────┘
  7780.  
  7781.    Figure 9-1.  The structure of an OS/2 FAT (MS-DOS─compatible) file system.
  7782.  
  7783. ╓┌─┌────────────────┌────────────────────────────────────────────────────────╖
  7784.    Function         Description
  7785.    ──────────────────────────────────────────────────────────────────────────
  7786.    Directory Search Operations
  7787.    DosFindFirst    Finds first matching file; returns a search handle
  7788.    DosFindNext     Finds next matching file using search handle
  7789.    DosFindClose    Closes a search handle
  7790.    DosSearchPath   Searches list of directories for file
  7791.  
  7792.    Directory Operations
  7793.    Function         Description
  7794.    ──────────────────────────────────────────────────────────────────────────
  7795.   Directory Operations
  7796.    DosChDir        Selects current directory
  7797.    DosMkDir        Creates a directory
  7798.    DosRmDir        Removes a directory
  7799.    DosMove         Renames a directory (also renames or moves files)
  7800.    DosQCurDir      Returns current directory
  7801.    DosQSysInfo     Returns maximum path length
  7802.  
  7803.    Drive Operations
  7804.    DosQCurDisk     Returns current disk drive
  7805.    DosSelectDisk   Selects current disk drive
  7806.  
  7807.    Volume Label Operations
  7808.    DosQFSInfo      Returns volume label, date and time of volume creation
  7809.    DosSetFSInfo    Adds or changes volume label
  7810.    ──────────────────────────────────────────────────────────────────────────
  7811.  
  7812.  
  7813.    Figure 9-2.  API functions for manipulation of drives, directories, and
  7814.    volume labels.
  7815.  
  7816.  
  7817.  Searching Disk Directories
  7818.  
  7819.    When you request a DosOpen operation on a file, you are performing an
  7820.    implicit search of a directory. OS/2 examines each entry of the directory
  7821.    to match the provided filename; if the file is found, OS/2 copies certain
  7822.    information from the directory into the system file table and returns a
  7823.    handle to the calling program. (The handle is really an indirect pointer
  7824.    to the SFT.) Thus, to test for the existence of a specific file, you can
  7825.    issue a call to DosOpen with the appropriate OpenMode and OpenFlag
  7826.    parameters and check for an error. (To avoid expending handles needlessly,
  7827.    follow with DosClose if the call to DosOpen succeeds.) Of course, this
  7828.    simplistic approach can give misleading results if the process is out of
  7829.    handles or if another process has opened the file with deny-all sharing
  7830.    mode.
  7831.  
  7832.    Most programs need to perform more elaborate (and more reliable) directory
  7833.    searches than the all-or-nothing capability provided by DosOpen. For
  7834.    example, a word processor might need to find and display all files with a
  7835.    DOC (document) extension so that the user can choose one from a menu. To
  7836.    satisfy such needs, OS/2 provides three functions that let you examine
  7837.    disk directories in an efficient hardware-independent fashion:
  7838.    DosFindFirst, DosFindNext, and DosFindClose.
  7839.  
  7840.    DosFindFirst accepts a file specification (which can include a drive,
  7841.    path, and wildcard characters) an attribute, the address and length of a
  7842.    buffer, and a maximum match count. It searches for the first matching file
  7843.    or files in the current or specified directory. If it finds no files with
  7844.    matching names and attributes, DosFindFirst returns an error. If it finds
  7845.    at least one match, it returns a search handle and a match count (1
  7846.    through the specified maximum) and fills the designated buffer with one or
  7847.    more entries in the format shown in Figure 9-3.
  7848.  
  7849.    In the FAT-based (MS-DOS─compatible) file system used in OS/2 versions 1.0
  7850.    and 1.1, some of the information listed in Figure 9-3 is not available in
  7851.    the disk directory. The dates and times of creation and last access are
  7852.    always returned as zero, and the file allocation is returned as the file
  7853.    size rounded up to agree with the size of the disk's allocation units.
  7854.  
  7855.    Note that when multiple matches are returned, the buffer entries do not
  7856.    have a constant length; the length of each record depends on the length of
  7857.    the filename. The number of matches returned is the minimum of the number
  7858.    available, the number requested, and the number that will fit into the
  7859.    buffer.
  7860.  
  7861.    A successful DosFindFirst must precede a call to DosFindNext. If the file
  7862.    specification that was previously passed to DosFindFirst included wildcard
  7863.    characters and if at least one matching file exists, DosFindNext can be
  7864.    called as many times as necessary with the search handle returned by
  7865.    DosFindFirst to locate all additional matching files. Like DosFindFirst,
  7866.    DosFindNext returns the file information in the calling program's buffer
  7867.    and uses the same format (Figure 9-3). When all matching files have been
  7868.    located, DosFindNext returns an error flag.
  7869.  
  7870.    Byte(s)                   Contents
  7871.    ──────────────────────────────────────────────────────────────────────────
  7872.    00H─01H                   File date of creation
  7873.    02H─03H                   File time of creation
  7874.    04H─05H                   File date of last access
  7875.    06H─07H                   File time of last access
  7876.    08H─09H                   File date of last write
  7877.    0AH─0BH                   File time of last write
  7878.    0CH─0FH                   File size
  7879.    10H─13H                   File allocation
  7880.    14H─15H                   File attribute
  7881.    16H                       Length of filename
  7882.    17H+                      Filename followed by null byte
  7883.    ──────────────────────────────────────────────────────────────────────────
  7884.  
  7885.    Figure 9-3.  Format of file information returned in designated buffer by
  7886.    DosFindFirst and DosFindNext.
  7887.  
  7888.    Both DosFindFirst and DosFindNext regard the attribute specified in the
  7889.    DosFindFirst call as inclusive rather than exclusive. If the read-only,
  7890.    hidden, system, archive, or directory attribute bits are set in the
  7891.    request, all normal files are returned in addition to the files or
  7892.    directories with the specified attribute. (The volume label attribute bit
  7893.    receives special treatment, as described later in this chapter.)
  7894.  
  7895.    After DosFindFirst or DosFindNext returns an error indicating that no more
  7896.    files match the specifications, the process should call DosFindClose to
  7897.    release the search handle. Although a large number of search handles
  7898.    (1000) are available for a single process, each active search handle has a
  7899.    system memory overhead and should therefore not be left active any longer
  7900.    than necessary. Figure 9-4 provides an example of searching a directory
  7901.    for all files with the ASM extension and displaying their names.
  7902.  
  7903.    ──────────────────────────────────────────────────────────────────────────
  7904.    stdout  equ     1               ; standard output handle
  7905.    cr      equ     0dh             ; ASCII carriage return
  7906.    lf      equ     0ah             ; ASCII linefeed
  7907.  
  7908.    _srec   struc                   ; search result structure...
  7909.    cdate   dw      ?               ; date of creation
  7910.    ctime   dw      ?               ; time of creation
  7911.    adate   dw      ?               ; date of last access
  7912.    atime   dw      ?               ; time of last access
  7913.    wdate   dw      ?               ; date of last write
  7914.    wtime   dw      ?               ; time of last write
  7915.    fsize   dd      ?               ; file size
  7916.    falloc  dd      ?               ; file allocation
  7917.    fattr   dw      ?               ; file attribute
  7918.    fcount  db      ?               ; filename count byte
  7919.    fname   db      13 dup (?)      ; ASCIIZ filename
  7920.    _srec   ends
  7921.  
  7922.  
  7923.    wlen    dw      ?               ; receives bytes written
  7924.  
  7925.    sname   db      '*.ASM',0       ; target name for search
  7926.  
  7927.    sbuf    _srec   <>              ; instantiate structure to
  7928.                                    ; receive search results
  7929.    sbuf_len equ    $-sbuf          ; length of result buffer
  7930.    dirhan  dw      ?               ; receives search handle
  7931.  
  7932.    schcnt  dw      ?               ; receives match count
  7933.  
  7934.    nl      db      cr,lf           ; newline (CR-LF pair)
  7935.    nl_len  equ     $-nl
  7936.  
  7937.            .
  7938.            .
  7939.            .
  7940.            mov     dirhan,-1       ; request search handle
  7941.  
  7942.            mov     schcnt,1        ; set max match count
  7943.  
  7944.                                    ; look for first match...
  7945.            push    ds              ; target name for search
  7946.            push    offset DGROUP:sname
  7947.            push    ds              ; receives search handle
  7948.            push    offset DGROUP:dirhan
  7949.            push    0               ; normal attribute
  7950.            push    ds              ; result buffer address
  7951.            push    offset DGROUP:sbuf
  7952.            push    sbuf_len        ; result buffer length
  7953.            push    ds              ; receives match count
  7954.            push    offset DGROUP:schcnt
  7955.            push    0               ; reserved DWORD 0
  7956.            push    0
  7957.            call    DosFindFirst    ; transfer to OS/2
  7958.            or      ax,ax           ; any files found?
  7959.            jnz     done            ; jump if no match
  7960.  
  7961.    display:                        ; output one filename...
  7962.            push    stdout          ; standard output handle
  7963.            push    ds              ; filename address
  7964.            push    offset DGROUP:sbuf.fname
  7965.            mov     al,sbuf.fcount
  7966.            xor     ah,ah
  7967.            push    ax              ; filename length
  7968.            push    ds              ; receives bytes written
  7969.            push    offset DGROUP:wlen
  7970.            call    DosWrite        ; transfer to OS/2
  7971.                                    ; send new line ...
  7972.            push    stdout          ; standard output handle
  7973.            push    ds              ; address of data
  7974.            push    offset DGROUP:nl
  7975.            push    nl_len          ; length of data
  7976.            push    ds              ; receives bytes written
  7977.            push    offset DGROUP:wlen
  7978.            call    DosWrite        ; transfer to OS/2
  7979.  
  7980.                                    ; search for next file...
  7981.            push    dirhan          ; search handle
  7982.            push    ds              ; result buffer address
  7983.            push    offset DGROUP:sbuf
  7984.            push    sbuf_len        ; result buffer length
  7985.            push    ds              ; receives match count
  7986.            push    offset schcnt
  7987.            call    DosFindNext     ; transfer to OS/2
  7988.            or      ax,ax           ; any more files?
  7989.            jz      display         ; jump if match found
  7990.  
  7991.    done:   push    dirhan          ; release search handle...
  7992.            call    DosFindClose    ; transfer to OS/2
  7993.            .
  7994.            .
  7995.            .
  7996.    ──────────────────────────────────────────────────────────────────────────
  7997.  
  7998.    Figure 9-4.  Using DosFindFirst and DosFindNext to find and display all
  7999.    files with the extension ASM. This simple example does not take advantage
  8000.    of OS/2's ability to return multiple matches on one function call.
  8001.  
  8002.    Note that OS/2 maintains the search context for each search handle in a
  8003.    table outside the process's memory space. Multiple interleaved searches,
  8004.    using different file specifications, can be in progress simultaneously,
  8005.    and OS/2 takes care of all the housekeeping details. The search handles
  8006.    are distinct from file and device handles and do not consume entries in
  8007.    the SFT.
  8008.  
  8009.    Before we move on from the topic of directory searches, we need to mention
  8010.    one more special-purpose OS/2 function. DosSearchPath accepts as
  8011.    parameters a filename and a list of directories (or the name of an
  8012.    environment variable whose value is a list of directories), and searches
  8013.    each directory in order. It returns the fully qualified pathname of the
  8014.    first match found. Although this function allows wildcard characters in
  8015.    the filename, it does not provide any way to continue the search and
  8016.    identify a set of files. It is therefore best suited to locating a
  8017.    particular file──such as a dynamic link library or a configuration file──
  8018.    that is vital to the execution of an application.
  8019.  
  8020.  
  8021.  Directory and Disk Control
  8022.  
  8023.    OS/2 provides three functions for selecting the current directory or for
  8024.    modifying the hierarchical directory structure: DosChDir (select the
  8025.    current directory), DosMkDir (create a directory), and DosRmDir (delete a
  8026.    directory).
  8027.  
  8028.    The directory functions require the address of an ASCIIZ pathname, which
  8029.    can include a drive code and which can be specified completely from the
  8030.    drive's root directory or relative to its current directory. All three
  8031.    directory functions return the usual status in register AX; all can fail
  8032.    for a variety of reasons.
  8033.  
  8034.    The most common cause for returning an error is that some element of the
  8035.    indicated path does not exist. DosRmDir requires special attention because
  8036.    it can return an error for reasons that are difficult to foresee or
  8037.    prevent in the requesting program. For example, the function fails if the
  8038.    directory being deleted contains any files or directories, or if it is the
  8039.    current directory for the command processor of some other screen group.
  8040.  
  8041.    Note that DosChDir selects the current directory for the requesting
  8042.    process only; it does not affect the current directory for other active
  8043.    processes (as you will quickly notice when your program terminates and the
  8044.    CMD.EXE prompt is redisplayed). The current directory is, however,
  8045.    inherited by child processes.
  8046.  
  8047.    An additional function, DosQCurDir, obtains the ASCIIZ pathname of the
  8048.    current directory for the specified or default disk drive. It is commonly
  8049.    used to build up fully qualified path and file specifications that can be
  8050.    displayed, saved across invocations of a program, or passed to other
  8051.    processes. DosQCurDir is called with a drive identifier (0 = default, 1 =
  8052.    A, and so forth), the address of a buffer to receive the pathname, and the
  8053.    address of a variable that contains the length of the buffer.
  8054.  
  8055.    The pathname returned by DosQCurDir does not contain a drive identifier or
  8056.    a leading backslash (\) character; if the current directory is the root
  8057.    directory, then a single null byte is returned. Also note that the
  8058.    function does not return the actual length of the pathname (as you might
  8059.    expect from the behavior of some other calls); your program must scan the
  8060.    string to determine its length.
  8061.  
  8062.    You can rename directories other than the root directory with the function
  8063.    DosMove, although this use is uncommon. The parameters for DosMove are the
  8064.    old and new ASCIIZ pathnames for the directories. The function fails if
  8065.    any element of the path for the old or new directory does not exist, or if
  8066.    the new pathname is the same as an existing file or directory. Directories
  8067.    cannot (unlike files) be moved from one location to another with DosMove.
  8068.  
  8069.    The functions DosQCurDisk and DosSelectDisk obtain or change the current
  8070.    drive. DosQCurDisk also returns a bitmap that indicates which logical
  8071.    drives exist in the system. DosSelectDisk affects only the requesting
  8072.    process; it does not affect the current disk for the current process's
  8073.    parent or any other processes active in the system. Like the current
  8074.    directory, the current drive is inherited by child processes.
  8075.  
  8076.  
  8077.  Volume Labels
  8078.  
  8079.    In FAT (MS-DOS─compatible) file systems, a volume label is an optional
  8080.    name, from 1 through 11 characters long, that the user assigns to a disk
  8081.    during a FORMAT operation or later with the LABEL command. You can display
  8082.    the volume label with the DIR, TREE, CHKDSK, LABEL, or VOL command, or you
  8083.    can change it with the LABEL command. The FORMAT program also assigns a
  8084.    semirandom 32-bit binary ID to each disk it formats, but this value is
  8085.    normally invisible to the user and cannot be altered.
  8086.  
  8087.    The distinction between volumes and drives in OS/2 might seem hazy, but it
  8088.    is important. A volume label is associated with a specific storage medium.
  8089.    A drive identifier (such as A:) is associated with a physical device on
  8090.    which a storage medium can be mounted. In the case of fixed disk drives,
  8091.    the medium associated with a drive identifier does not change (hence the
  8092.    name). In the case of floppy disks or other removable media, however, the
  8093.    disk accessed with a given drive identifier might have any volume label or
  8094.    none at all.
  8095.  
  8096.    As a consequence of this distinction, volume labels do not take the place
  8097.    of the single character drive identifier and cannot be used as part of a
  8098.    pathname to identify a file. In current versions of OS/2, their only real
  8099.    use is to identify (inconclusively) removable disks for users and
  8100.    application programs and to help the floppy disk driver detect that a disk
  8101.    has been changed with a file open.
  8102.  
  8103.    In FAT file systems, a volume label is stored as a special type of entry
  8104.    in the root directory with an attribute of 08H. Volume labels cannot,
  8105.    however, be created with DosOpen or located with DosFindFirst──an
  8106.    attribute of 08H is rejected by these functions. You must use the
  8107.    functions DosQFSInfo and DosSetFSInfo to add, change, or inspect a volume
  8108.    label (Figure 9-5 on the following page).
  8109.  
  8110.    ──────────────────────────────────────────────────────────────────────────
  8111.    stdout  equ     1               ; standard output handle
  8112.  
  8113.    _vlinf  struc                   ; volume label structure
  8114.    vdate   dw      ?               ; date of creation
  8115.    vtime   dw      ?               ; time of creation
  8116.    vcount  db      ?               ; length of volume name
  8117.    vname   db      13 dup (?)      ; ASCIIZ volume name
  8118.    _vlinf  ends
  8119.  
  8120.  
  8121.    wlen    dw      ?               ; receives bytes written
  8122.  
  8123.    vlinf   _vlinf  <>              ; instantiate structure to
  8124.                                    ; receive volume info
  8125.    vlinf_len equ   $-vlinf         ; length of buffer
  8126.  
  8127.            .
  8128.            .
  8129.            .
  8130.                                    ; get volume label...
  8131.            push    0               ; drive = default
  8132.            push    2               ; file info level 2
  8133.            push    ds              ; buffer receives label
  8134.            push    offset DGROUP:vlinf
  8135.            push    vlinf_len       ; size of buffer
  8136.            call    DosQFSInfo      ; transfer to OS/2
  8137.            or      ax,ax           ; call successful?
  8138.            jnz     done            ; jump if no volume label
  8139.  
  8140.                                    ; display volume label...
  8141.            push    stdout          ; standard output handle
  8142.            push    ds              ; volume label address
  8143.            push    offset DGROUP:vlinf.vname
  8144.            mov     al,vlinf.vcount ; volume label length
  8145.            xor     ah,ah
  8146.            push    ax
  8147.            push    ds              ; receives bytes written
  8148.            push    offset DGROUP:wlen
  8149.            call    DosWrite        ; transfer to OS/2
  8150.  
  8151.    done:   .
  8152.            .
  8153.            .
  8154.    ──────────────────────────────────────────────────────────────────────────
  8155.  
  8156.    Figure 9-5.  Using DosQFSInfo to obtain and display a volume label.
  8157.  
  8158.  
  8159.  Sample Program: WHEREIS
  8160.  
  8161.    The listings WHEREIS.C (Figure 9-6) and WHEREIS.ASM (Figure 9-7 on p.
  8162.    179) contain the source code for an OS/2 "file finder" utility. WHEREIS
  8163.    uses DosFindFirst, DosFindNext, and DosFindClose to perform a recursive
  8164.    search of the directory structure for a pathname (which may include
  8165.    wildcard characters). It displays the names of any matching files on the
  8166.    standard output, which can be directed to a file or to another character
  8167.    device.
  8168.  
  8169.    ──────────────────────────────────────────────────────────────────────────
  8170.    /*
  8171.            WHEREIS.C
  8172.  
  8173.            A file finder utility that searches the current drive, starting
  8174.            with the root directory (\), for the specified pathname.
  8175.            Wildcard characters can be included.
  8176.  
  8177.            Compile with:  C> cl whereis.c
  8178.  
  8179.            Usage is:  C> whereis pathname
  8180.  
  8181.            Copyright (C) 1988 Ray Duncan
  8182.    */
  8183.  
  8184.  
  8185.    #include <stdio.h>
  8186.    #include <string.h>
  8187.  
  8188.    #define API unsigned extern far pascal
  8189.  
  8190.    API DosChDir(char far *, unsigned long);
  8191.    API DosFindClose(unsigned);
  8192.    API DosFindFirst(char far *, unsigned far *, unsigned, void far *,
  8193.                     int, int far *, unsigned long);
  8194.    API DosFindNext(unsigned, void far *, int, int far *);
  8195.    API DosQCurDisk(int far *, unsigned long far *);
  8196.    API DosQCurDir(int, void far *, int far *);
  8197.    API DosSelectDisk(int);
  8198.    API DosWrite(unsigned, void far *, unsigned, unsigned far *);
  8199.  
  8200.    #define NORM      0x00                  /* file attribute bits */
  8201.    #define RD_ONLY   0x01
  8202.    #define HIDDEN    0x02
  8203.    #define SYSTEM    0x04
  8204.    #define DIR       0x10
  8205.    #define ARCHIVE   0x20
  8206.  
  8207.    struct _srec {                          /* used by DosFindFirst */
  8208.                        unsigned cdate;     /* and DosFindNext */
  8209.                        unsigned ctime;
  8210.                        unsigned adate;
  8211.                        unsigned atime;
  8212.                        unsigned wdate;
  8213.                        unsigned wtime;
  8214.                        long fsize;
  8215.                        long falloc;
  8216.                        unsigned fattr;
  8217.                        char fcount;
  8218.                        char fname[13];
  8219.                 }
  8220.                    sbuf;                   /* receives search results */
  8221.  
  8222.    int drvno;                              /* current drive */
  8223.    int count = 0;                          /* total files matched */
  8224.    char sname[80];                         /* target pathname */
  8225.  
  8226.  
  8227.    main(int argc, char *argv[])
  8228.    {
  8229.        unsigned long drvmap;               /* logical drive map */
  8230.  
  8231.        if(argc < 2)                        /* exit if no parameters */
  8232.        {
  8233.            printf("\nwhereis: missing filename\n");
  8234.            exit(1);
  8235.        }
  8236.  
  8237.        DosQCurDisk(&drvno, &drvmap);       /* get current drive */
  8238.  
  8239.                                            /* any drive specified? */
  8240.        if(((strlen(argv[1])) >= 2) && ((argv[1])[1] == ':'))
  8241.        {
  8242.                                            /* get binary drive code */
  8243.            drvno = ((argv[1]) [0] | 0x20) - ('a'-1);
  8244.  
  8245.            if(DosSelectDisk(drvno))        /* select drive or exit */
  8246.            {
  8247.                printf("\nwhereis: bad drive\n");
  8248.                exit(1);
  8249.            }
  8250.            argv[1] += 2;                   /* advance past drive */
  8251.        }
  8252.  
  8253.        strcpy(sname,argv[1]);              /* save search target */
  8254.  
  8255.        schdir("\\");                       /* start search with root */
  8256.  
  8257.                                            /* advise if no matches */
  8258.        if(count == 0) printf("\nwhereis: no files\n");
  8259.    }
  8260.  
  8261.  
  8262.    /*
  8263.        SCHDIR: search directory for matching files and
  8264.                any other directories
  8265.    */
  8266.  
  8267.    schdir(char *dirname)
  8268.    {
  8269.        unsigned shan = -1;                 /* search handle */
  8270.        int scnt = 1;                       /* max search matches */
  8271.  
  8272.        DosChDir(dirname, 0L);              /* select new directory */
  8273.  
  8274.        schfile();                          /* find and list files */
  8275.  
  8276.                                            /* search for directories */
  8277.        if(!DosFindFirst("*.*", &shan, NORM|DIR, &sbuf, sizeof(sbuf), &scnt, 0L
  8278.        {
  8279.            do                              /* if found directory other */
  8280.            {                               /* than . and .. aliases */
  8281.                if((sbuf.fattr & DIR) && (sbuf.fname[0] != '.'))
  8282.                {
  8283.                    schdir(sbuf.fname);     /* search the directory */
  8284.                    DosChDir("..", 0L);     /* restore old directory */
  8285.                }
  8286.                                            /* look for more directories */
  8287.            } while(DosFindNext(shan, &sbuf, sizeof(sbuf), &scnt) == 0);
  8288.        }
  8289.  
  8290.        DosFindClose(shan);                 /* close search handle */
  8291.    }
  8292.    /*
  8293.        SCHFILE: search current directory for files
  8294.                 matching string in 'sname'
  8295.    */
  8296.  
  8297.    schfile()
  8298.    {
  8299.        unsigned shan = -1;                 /* search handle */
  8300.        int scnt = 1;                       /* max search matches */
  8301.  
  8302.        if(!DosFindFirst(sname, &shan, NORM, &sbuf, sizeof(sbuf), &scnt, 0L))
  8303.        {
  8304.            do pfile();                     /* list matching files */
  8305.            while(DosFindNext(shan, &sbuf, sizeof(sbuf), &scnt) == 0);
  8306.        }
  8307.  
  8308.        DosFindClose(shan);                 /* close search handle */
  8309.    }
  8310.  
  8311.  
  8312.    /*
  8313.        PFILE: display current drive and directory,
  8314.               followed by filename from 'sbuf.fname'
  8315.    */
  8316.  
  8317.    pfile()
  8318.    {
  8319.        count++;                            /* count matched files */
  8320.        pdir();                             /* list drive and path */
  8321.        printf("%s\n", strlwr(sbuf.fname)); /* list filename */
  8322.    }
  8323.  
  8324.  
  8325.    /*
  8326.        PDIR: display current drive and directory
  8327.    */
  8328.  
  8329.    pdir()
  8330.    {
  8331.        char dbuf[80];                      /* receives current dir */
  8332.        int dlen = sizeof(dbuf);            /* length of buffer */
  8333.  
  8334.        DosQCurDir(0, dbuf, &dlen);         /* get current directory */
  8335.        if(strlen(dbuf) != 0)               /* add backslash to */
  8336.            strcat(dbuf,"\\");              /* directory if not root */
  8337.  
  8338.                                            /* display drive and path */
  8339.        printf("%c:\\%s", drvno+'a'-1, strlwr(dbuf));
  8340.    }
  8341.    ──────────────────────────────────────────────────────────────────────────
  8342.  
  8343.    Figure 9-6.  WHEREIS.C, the C language source code for the WHEREIS.EXE
  8344.    utility.
  8345.  
  8346.    ──────────────────────────────────────────────────────────────────────────
  8347.            title   WHEREIS -- File Finder Utility
  8348.            page    55,132
  8349.            .286
  8350.  
  8351.    ;
  8352.    ; WHEREIS.ASM
  8353.    ;
  8354.    ; A file finder utility that searches the current drive, starting
  8355.    ; with the root directory (\), for the specified pathname.
  8356.    ; Wildcard characters can be included.
  8357.    ;
  8358.    ; This program requires the modules ARGV.ASM and ARGC.ASM.
  8359.    ;
  8360.    ; Assemble with:  C> masm whereis.asm;
  8361.    ; Link with:  C> link whereis+argv+argc,,,os2,whereis
  8362.    ;
  8363.    ; Usage is:  C> whereis pathname
  8364.    ;
  8365.    ; Copyright (C) 1988 Ray Duncan
  8366.    ;
  8367.  
  8368.    stdin   equ     0                       ; standard input handle
  8369.    stdout  equ     1                       ; standard output handle
  8370.    stderr  equ     2                       ; standard error handle
  8371.  
  8372.    cr      equ     0dh                     ; ASCII carriage return
  8373.    lf      equ     0ah                     ; ASCII linefeed
  8374.  
  8375.            extrn   DosChDir:far
  8376.            extrn   DosExit:far
  8377.            extrn   DosFindClose:far
  8378.            extrn   DosFindFirst:far
  8379.            extrn   DosFindNext:far
  8380.            extrn   DosQCurDir:far
  8381.            extrn   DosQCurDisk:far
  8382.            extrn   DosSelectDisk:far
  8383.            extrn   DosWrite:far
  8384.  
  8385.    _srec   struc                           ; search result structure...
  8386.    cdate   dw      ?                       ; date of creation
  8387.    ctime   dw      ?                       ; time of creation
  8388.    adate   dw      ?                       ; date of last access
  8389.    atime   dw      ?                       ; time of last access
  8390.    wdate   dw      ?                       ; date of last write
  8391.    wtime   dw      ?                       ; time of last write
  8392.    fsize   dd      ?                       ; file size
  8393.    falloc  dd      ?                       ; file allocation
  8394.    fattr   dw      ?                       ; file attribute
  8395.    fcount  db      ?                       ; filename count byte
  8396.    fname   db      13 dup (?)              ; ASCIIZ filename
  8397.    _srec   ends
  8398.  
  8399.  
  8400.    DGROUP  group   _DATA
  8401.  
  8402.    _DATA   segment word public 'DATA'
  8403.  
  8404.    root    db      '\',0                   ; name of root directory
  8405.  
  8406.    parent  db      '..',0                  ; alias for parent directory
  8407.  
  8408.    wild    db      '*.*',0                 ; matches all files
  8409.  
  8410.    sname   db      64 dup (0)              ; filename for search
  8411.  
  8412.    drvno   dw      0                       ; current drive
  8413.    drvmap  dd      0                       ; logical drive bitmap
  8414.  
  8415.    dname   db      'X:\'                   ; current drive ID
  8416.    dbuf    db      80 dup (?)              ; current directory
  8417.    dbuf_len dw     ?                       ; length of buffer
  8418.  
  8419.    sbuf    _srec   <>                      ; receives search results
  8420.    sbuf_len equ $-sbuf
  8421.  
  8422.    count   dw      0                       ; total files matched
  8423.    wlen    dw      ?                       ; receives bytes written
  8424.    shandle dw      -1                      ; directory search handle
  8425.    scount  dw      1                       ; number of files to return
  8426.    msg1    db      cr,lf
  8427.            db      'whereis: no files found'
  8428.            db      cr,lf
  8429.    msg1_len equ $-msg1
  8430.  
  8431.    msg2    db      cr,lf
  8432.    msg2_len equ $-msg2
  8433.  
  8434.    msg3    db      cr,lf
  8435.            db      'whereis: missing filename'
  8436.            db      cr,lf
  8437.    msg3_len equ $-msg3
  8438.  
  8439.    msg4    db      cr,lf
  8440.            db      'whereis: bad drive'
  8441.            db      cr,lf
  8442.    msg4_len equ $-msg4
  8443.  
  8444.    _DATA   ends
  8445.  
  8446.  
  8447.    _TEXT   segment word public 'CODE'
  8448.  
  8449.            assume  cs:_TEXT,ds:DGROUP
  8450.  
  8451.            extrn   argv:near
  8452.            extrn   argc:near
  8453.  
  8454.    whereis proc    far
  8455.  
  8456.            call    argc                    ; filename present in
  8457.            cmp     ax,2                    ; command tail?
  8458.            jae     where1                  ; jump, filename present
  8459.  
  8460.                                            ; no filename, exit...
  8461.            mov     dx,offset DGROUP:msg3   ; error message address
  8462.            mov     cx,msg3_len             ; message length
  8463.            jmp     where6                  ; go terminate
  8464.  
  8465.    where1:                                 ; get current drive...
  8466.            push    ds                      ; receives drive code
  8467.            push    offset DGROUP:drvno
  8468.            push    ds                      ; receives drive bitmap
  8469.            push    offset DGROUP:drvmap
  8470.            call    DosQCurDisk             ; transfer to OS/2
  8471.            mov     ax,1                    ; get address and length
  8472.            call    argv                    ; of filename parameter
  8473.                                            ; returns ES:BX = address
  8474.                                            ;         AX    = length
  8475.            mov     cx,ax                   ; save length in CX
  8476.  
  8477.            cmp     ax,2                    ; parameter length > 2?
  8478.            jle     where3                  ; no, jump
  8479.            cmp     byte ptr es:[bx+1],':'  ; drive delimiter present?
  8480.            jne     where3                  ; no, jump
  8481.  
  8482.            mov     al,es:[bx]              ; get ASCII drive code
  8483.            or      al,20h                  ; fold to lowercase
  8484.            xor     ah,ah
  8485.            sub     ax,'a'-1                ; convert drive code to
  8486.            mov     drvno,ax                ; binary and save it
  8487.            cmp     ax,1                    ; make sure drive valid
  8488.            jb      where2                  ; jump, bad drive
  8489.            cmp     ax,26
  8490.            ja      where2                  ; jump, bad drive
  8491.  
  8492.            add     bx,2                    ; advance command tail
  8493.            sub     cx,2                    ; pointer past drive code
  8494.  
  8495.                                            ; set drive for search...
  8496.            push    ax                      ; drive code
  8497.            call    DosSelectDisk           ; transfer to OS/2
  8498.            or      ax,ax                   ; drive OK?
  8499.            jz      where3                  ; jump, drive was valid
  8500.  
  8501.                                            ; bad drive, exit...
  8502.    where2: mov     dx,offset DGROUP:msg4   ; error message address
  8503.            mov     cx,msg4_len             ; message length
  8504.            jmp     where6
  8505.  
  8506.    where3: mov     di,offset DGROUP:sname  ; DS:DI = local buffer
  8507.  
  8508.    where4: mov     al,es:[bx]              ; copy filename to local
  8509.            mov     [di],al                 ; buffer byte by byte...
  8510.            inc     di
  8511.            inc     bx
  8512.            loop    where4
  8513.  
  8514.            mov     byte ptr [di],0         ; append null byte
  8515.            push    ds                      ; make DGROUP addressable
  8516.            pop     es                      ; with ES
  8517.            assume  es:DGROUP
  8518.            mov     dx,offset DGROUP:root   ; start searching with
  8519.            call    schdir                  ; the root directory
  8520.  
  8521.            cmp     count,0                 ; any matching files found?
  8522.            jne     where5                  ; yes, exit silently
  8523.  
  8524.                                            ; no, display 'no files'...
  8525.            push    stdout                  ; standard output handle
  8526.            push    ds                      ; message address
  8527.            push    offset DGROUP:msg1
  8528.            push    msg1_len                ; message length
  8529.            push    ds                      ; receives bytes written
  8530.            push    offset DGROUP:wlen
  8531.            call    DosWrite                ; transfer to OS/2
  8532.  
  8533.    where5:                                 ; final exit to OS/2...
  8534.            push    1                       ; terminate all threads
  8535.            push    0                       ; return code=0 (success)
  8536.            call    DosExit                 ; transfer to OS/2
  8537.  
  8538.    where6:                                 ; common error exit...
  8539.                                            ; DS:DX = msg, CX = length
  8540.            push    stderr                  ; standard output handle
  8541.            push    ds                      ; address of message
  8542.            push    dx
  8543.            push    cx                      ; length of message
  8544.            push    ds                      ; receives bytes written
  8545.            push    offset DGROUP:wlen
  8546.            call    DosWrite                ; transfer to OS/2
  8547.  
  8548.                                            ; final exit to OS/2...
  8549.            push    1                       ; terminate all threads
  8550.            push    1                       ; exit code = 1 (error)
  8551.            call    DosExit                 ; transfer to OS/2
  8552.  
  8553.    whereis endp
  8554.  
  8555.  
  8556.    ; SCHDIR:       search a directory for matching
  8557.    ;               files and any other directories
  8558.    ;
  8559.    ; Call with:    DS:DX = ASCIIZ directory name
  8560.    ; Returns:      nothing
  8561.    ; Uses:         all registers
  8562.    schdir  proc    near
  8563.  
  8564.            push    shandle                 ; save old search handle
  8565.            mov     shandle,-1              ; initialize search handle
  8566.  
  8567.                                            ; set search directory...
  8568.            push    ds                      ; directory name address
  8569.            push    dx
  8570.            push    0                       ; reserved DWORD 0
  8571.            push    0
  8572.            call    DosChDir                ; transfer to OS/2
  8573.  
  8574.            call    schfile                 ; search current directory
  8575.                                            ; for matching files
  8576.  
  8577.                                            ; search for directories...
  8578.            mov     scount,1                ; max matches to return
  8579.            push    ds                      ; target name address
  8580.            push    offset DGROUP:wild
  8581.            push    ds                      ; receives search handle
  8582.            push    offset DGROUP:shandle
  8583.            push    10h                     ; normal and dir attribute
  8584.            push    ds                      ; result buffer address
  8585.            push    offset DGROUP:sbuf
  8586.            push    sbuf_len                ; result buffer length
  8587.            push    ds                      ; receives match count
  8588.            push    offset DGROUP:scount
  8589.            push    0                       ; reserved DWORD 0
  8590.            push    0
  8591.            call    DosFindFirst            ; transfer to OS/2
  8592.  
  8593.            or      ax,ax                   ; find anything?
  8594.            jnz     schdir3                 ; no, jump
  8595.  
  8596.    schdir1:                                ; found some match...
  8597.            test    sbuf.fattr,10h          ; is it a directory?
  8598.            jz      schdir2                 ; no, skip it
  8599.  
  8600.            cmp     sbuf.fname,'.'          ; is it . or .. entry?
  8601.            je      schdir2                 ; yes, skip it
  8602.                                            ; no, new directory found
  8603.            mov     dx,offset DGROUP:sbuf.fname
  8604.            call    schdir                  ; call self to search it
  8605.                                            ; restore old directory...
  8606.            push    ds                      ; address of '..' alias
  8607.            push    offset DGROUP:parent
  8608.            push    0                       ; reserved DWORD 0
  8609.            push    0
  8610.            call    DosChDir                ; transfer to OS/2
  8611.  
  8612.    schdir2:                                ; found at least one match,
  8613.                                            ; look for next match...
  8614.            mov     scount,1                ; max matches to return
  8615.            push    shandle                 ; handle from DosFindFirst
  8616.            push    ds                      ; result buffer address
  8617.            push    offset DGROUP:sbuf
  8618.            push    sbuf_len                ; result buffer length
  8619.            push    ds                      ; receives match count
  8620.            push    offset DGROUP:scount
  8621.            call    DosFindNext             ; transfer to OS/2
  8622.  
  8623.            or      ax,ax                   ; any matches found?
  8624.            jz      schdir1                 ; yes, go process it
  8625.  
  8626.    schdir3:                                ; end of search...
  8627.            push    shandle                 ; close search handle
  8628.            call    DosFindClose            ; transfer to OS/2
  8629.  
  8630.            pop     shandle                 ; restore previous handle
  8631.  
  8632.            ret                             ; back to caller
  8633.  
  8634.    schdir  endp
  8635.  
  8636.  
  8637.    ; SCHFILE:      search current directory for
  8638.    ;               files matching string in 'sname'
  8639.    ;
  8640.    ; Call with:    nothing
  8641.    ; Returns:      nothing
  8642.    ; Uses:         all registers
  8643.  
  8644.    schfile proc    near
  8645.  
  8646.            push    shandle                 ; save previous handle
  8647.            mov     shandle,-1              ; initialize search handle
  8648.            mov     scount,1                ; max matches to return
  8649.            push    ds                      ; name to match
  8650.            push    offset DGROUP:sname
  8651.            push    ds                      ; receives search handle
  8652.            push    offset DGROUP:shandle
  8653.            push    0h                      ; attribute = normal files
  8654.            push    ds                      ; result buffer address
  8655.            push    offset DGROUP:sbuf
  8656.            push    sbuf_len                ; result buffer length
  8657.            push    ds                      ; receives match count
  8658.            push    offset DGROUP:scount
  8659.            push    0                       ; reserved DWORD 0
  8660.            push    0
  8661.            call    DosFindFirst            ; transfer to OS/2
  8662.  
  8663.            or      ax,ax                   ; any matches found?
  8664.            jnz     schfile3                ; no, terminate search
  8665.  
  8666.    schfile1:                               ; found matching file...
  8667.            call    pfile                   ; display its name
  8668.  
  8669.                                            ; look for next match...
  8670.            push    shandle                 ; handle from DosFindFirst
  8671.            push    ds                      ; result buffer address
  8672.            push    offset DGROUP:sbuf
  8673.            push    sbuf_len                ; result buffer length
  8674.            push    ds                      ; receives match count
  8675.            push    offset DGROUP:scount
  8676.            call    DosFindNext             ; transfer to OS/2
  8677.  
  8678.            or      ax,ax                   ; any more matches?
  8679.            jz      schfile1                ; yes, go display filename
  8680.  
  8681.    schfile3:                               ; end of search...
  8682.            push    shandle                 ; close search handle
  8683.            call    DosFindClose            ; transfer to OS/2
  8684.  
  8685.            pop     shandle                 ; restore previous handle
  8686.            ret                             ; return to caller
  8687.  
  8688.    schfile endp
  8689.  
  8690.    ; PFILE:        display current drive and directory,
  8691.    ;               followed by filename from 'sbuf.fname'
  8692.    ;
  8693.    ; Call with:    nothing
  8694.    ; Returns:      nothing
  8695.    ; Uses:         all registers
  8696.    pfile   proc    near
  8697.  
  8698.            inc     count                   ; count matched files
  8699.  
  8700.            call    pdir                    ; display drive:path
  8701.  
  8702.                                            ; fold name to lower case
  8703.            mov     bx,offset DGROUP:sbuf.fname
  8704.            call    makelc
  8705.  
  8706.                                            ; display filename...
  8707.            push    stdout                  ; standard output handle
  8708.            push    ds                      ; filename address
  8709.            push    offset DGROUP:sbuf.fname
  8710.            mov     al,sbuf.fcount          ; filename length
  8711.            xor     ah,ah
  8712.            push    ax
  8713.            push    ds                      ; receives bytes written
  8714.            push    offset DGROUP:wlen
  8715.            call    DosWrite                ; transfer to OS/2
  8716.  
  8717.                                            ; send newline sequence...
  8718.            push    stdout                  ; standard output handle
  8719.            push    ds                      ; address of newline
  8720.            push    offset DGROUP:msg2
  8721.            push    msg2_len                ; length of newline
  8722.            push    ds                      ; receives bytes written
  8723.            push    offset DGROUP:wlen
  8724.            call    DosWrite                ; transfer to OS/2
  8725.  
  8726.            ret                             ; return to caller
  8727.  
  8728.    pfile   endp
  8729.  
  8730.    ; PDIR:         display current drive and directory
  8731.    ;
  8732.    ; Call with:    nothing
  8733.    ; Returns:      nothing
  8734.    ; Uses:         AX, BX, CX, DI, ES
  8735.  
  8736.    pdir    proc    near
  8737.            mov     ax,drvno                ; convert binary drive
  8738.            add     al,'A'-1                ; code to ASCII drive
  8739.            mov     dname,al                ; and store it for output
  8740.            mov     dbuf_len,dbuf_len-dbuf  ; initialize length of
  8741.                                            ; directory buffer
  8742.  
  8743.                                            ; get current directory...
  8744.            push    0                       ; drive 0 = default
  8745.            push    ds                      ; receives ASCIIZ path
  8746.            push    offset DGROUP:dbuf
  8747.            push    ds                      ; contains buffer length
  8748.            push    offset DGROUP:dbuf_len
  8749.            call    DosQCurDir              ; transfer to OS/2
  8750.  
  8751.            mov     di,offset DGROUP:dbuf   ; address of path
  8752.  
  8753.            cmp     byte ptr [di],0         ; is path = root?
  8754.            je      pdir1                   ; yes, jump
  8755.  
  8756.            mov     cx,dbuf_len-dbuf        ; no, scan for null
  8757.            xor     al,al                   ; byte at end of path...
  8758.            repne scasb
  8759.  
  8760.            mov     byte ptr [di-1],'\'     ; append a backslash
  8761.  
  8762.    pdir1:  mov     bx,offset DGROUP:dname  ; fold everything to
  8763.            call    makelc                  ; lowercase
  8764.  
  8765.                                            ; now display drive:path...
  8766.            push    stdout                  ; standard output handle
  8767.            push    ds                      ; address of pathname
  8768.            push    offset DGROUP:dname
  8769.            sub     di,offset DGROUP:dname  ; length of drive and path
  8770.            push    di
  8771.            push    ds                      ; receives bytes written
  8772.            push    offset DGROUP:wlen
  8773.            call    DosWrite                ; transfer to OS/2
  8774.  
  8775.            ret                             ; back to caller
  8776.  
  8777.    pdir    endp
  8778.  
  8779.    ; MAKELC:       convert ASCIIZ string to lowercase
  8780.    ;
  8781.    ; Call with:    DS:BX = string address
  8782.    ; Returns:      nothing
  8783.    ; Uses:         BX
  8784.    makelc  proc    near
  8785.  
  8786.    make1:  cmp     byte ptr [bx],0         ; end of string?
  8787.            je      make3                   ; jump if end
  8788.  
  8789.            cmp     byte ptr [bx],'A'       ; check next character
  8790.            jb      make2                   ; jump, not uppercase
  8791.            cmp     byte ptr [bx],'Z'
  8792.            ja      make2                   ; jump, not uppercase
  8793.  
  8794.            or      byte ptr [bx],20h       ; fold to lowercase
  8795.  
  8796.    make2:  inc     bx                      ; advance through string
  8797.            jmp     make1
  8798.  
  8799.    make3:  ret                             ; back to caller
  8800.  
  8801.    makelc  endp
  8802.  
  8803.    _TEXT   ends
  8804.  
  8805.            end     whereis
  8806.    ──────────────────────────────────────────────────────────────────────────
  8807.  
  8808.    Figure 9-7.  WHEREIS.ASM, the MASM source code for the WHEREIS.EXE
  8809.    utility. The source code for the subroutines ARGV.ASM and ARGC.ASM can be
  8810.    found in Chapter 8.
  8811.  
  8812.    WHEREIS has four major subroutines. A search begins when the program's
  8813.    main routine calls the first subroutine, schdir, with a plain ASCIIZ
  8814.    backslash (\) signifying the root directory for the current or specified
  8815.    drive. The schdir subroutine makes that directory current using DosChDir
  8816.    and calls schfile to find and display any matching files. The subroutine
  8817.    pfile, which is called by schfile, works together with pdir to display a
  8818.    fully qualified pathname. It obtains the current drive and directory with
  8819.    DosQCurDisk and DosQCurDrive, and then formats and displays them along
  8820.    with the filename that was discovered by schfile. The schdir subroutine
  8821.    then searches the current directory for other directories and calls itself
  8822.    recursively with the name of each directory found.
  8823.  
  8824.    To compile and link the source file WHEREIS.C into the executable file
  8825.    WHEREIS.EXE, enter the command:
  8826.  
  8827.    [C:\] CL WHEREIS.C  <Enter>
  8828.  
  8829.    To assemble and link the files WHEREIS.ASM and WHEREIS.DEF (Figure 9-8)
  8830.    into the executable file WHEREIS.EXE, you also need the modules ARGC.ASM
  8831.    and ARGV.ASM from Chapter 8. Gather the four files together in the same
  8832.    directory and enter the commands:
  8833.  
  8834.    [C:\] MASM WHEREIS.ASM;  <Enter>
  8835.    [C:\] MASM ARGC.ASM;  <Enter>
  8836.    [C:\] MASM ARGV.ASM;  <Enter>
  8837.    [C:\] LINK WHEREIS+ARGC+ARGV,,,OS2,WHEREIS  <Enter>
  8838.  
  8839.    To run either version of the utility, enter a command of the following
  8840.    form:
  8841.  
  8842.    [C:\] WHEREIS pathname.ext  <Enter>
  8843.  
  8844.    If no matching files are found, or if the command line argument is
  8845.    missing, an appropriate error message is displayed.
  8846.  
  8847.    The recursive use of DosFindFirst, DosFindNext, and DosFindClose in the
  8848.    WHEREIS program is not compatible with the Family API restrictions. Thus,
  8849.    WHEREIS cannot be bound for use in real mode. WHEREIS could be made
  8850.    considerably more efficient by eliminating the calls to DosChDir and by
  8851.    requesting more than one match per call to DosFindFirst and DosFindNext;
  8852.    these enhancements have been left, so the saying goes, as exercises for
  8853.    the reader.
  8854.  
  8855.    ──────────────────────────────────────────────────────────────────────────
  8856.    NAME WHEREIS WINDOWCOMPAT
  8857.    PROTMODE
  8858.    STACKSIZE 4096
  8859.    ──────────────────────────────────────────────────────────────────────────
  8860.  
  8861.    Figure 9-8.  WHEREIS.DEF, the module definition file for the WHEREIS.EXE
  8862.    utility.
  8863.  
  8864.  
  8865.  
  8866.  ────────────────────────────────────────────────────────────────────────────
  8867.  Chapter 10  Disk Internals
  8868.  
  8869.    Block storage devices for OS/2 versions 1.0 and 1.1 are organized as FAT
  8870.    (file allocation table) file systems, compatible with MS-DOS versions 2.0
  8871.    and later. This is convenient for software developers because they can
  8872.    more easily transport data between MS-DOS and OS/2 systems and develop
  8873.    programs for MS-DOS under OS/2 (and vice versa).
  8874.  
  8875.    FAT file systems are organized according to a rigid scheme that is easily
  8876.    understood and manipulated. Although most programmers never need to access
  8877.    the special control areas of such a device directly, an understanding of
  8878.    their structure leads to a better understanding of constraints on file
  8879.    size and file system behavior.
  8880.  
  8881.  
  8882.  Distinct Volume Areas
  8883.  
  8884.    OS/2 views each logical volume──whether it corresponds to an entire
  8885.    physical medium (such as a diskette) or only a part of a larger fixed
  8886.    disk── as a continuous sequence of logical sectors, starting with sector
  8887.    0. (You can also implement a "logical disk volume" on other types of
  8888.    storage; for example, RAM disks map a disk structure onto a block of
  8889.    memory.) The available sectors are divided among several areas of fixed
  8890.    size as shown in Figure 10-1 on the following page:
  8891.  
  8892.    ■  The boot sector
  8893.  
  8894.    ■  An optional reserved area
  8895.  
  8896.    ■  One or more FATs
  8897.  
  8898.    ■  The root directory
  8899.  
  8900.    ■  The files area
  8901.  
  8902.    OS/2 translates file management requests from application programs into
  8903.    requests to the disk driver for transfers of logical sectors; in doing so,
  8904.    it uses the information in the volume's boot sector, FAT, and root
  8905.    directory. The sizes of these areas can vary from disk to disk, but all
  8906.    the information needed to interpret the structure of a particular disk is
  8907.    on the disk itself.
  8908.  
  8909.    The disk driver maps logical sectors onto actual physical addresses (head,
  8910.    cylinder, and sector). It is the operating system module that controls the
  8911.    disk by reading and writing the I/O ports assigned to its adapter. Any
  8912.    interleaving (the mapping of consecutive physical sector addresses onto
  8913.    noncontiguous physical sectors to improve performance) is done at the
  8914.    adapter level.
  8915.  
  8916.    OS/2 disk drivers are typically written in assembly language. They must be
  8917.    capable of functioning in both real and protected mode and are carefully
  8918.    tuned for optimum performance. OS/2 always installs the "base" driver for
  8919.    IBM-compatible disk devices (DISK01.SYS or DISK02.SYS) during system
  8920.    initialization. To load disk drivers supplied by manufacturers of
  8921.    non-IBM-compatible disk drives, include a DEVICE= statement in the
  8922.    CONFIG.SYS file on the boot disk.
  8923.  
  8924.     ┌───────────────────────────────────────┐
  8925.     │              Boot sector              │
  8926.     │             Reserved area             │
  8927.     ├───────────────────────────────────────┤
  8928.     │                                       │
  8929.     │       File allocation table #1        │
  8930.     │                                       │
  8931.     ├───────────────────────────────────────┤
  8932.     │                                       │
  8933.     │ Possible additional copies of the FAT │
  8934.     │                                       │
  8935.     ├───────────────────────────────────────┤
  8936.     │                                       │
  8937.     │            Root directory             │
  8938.     │                                       │
  8939.     ├───────────────────────────────────────┤
  8940.     │                                       │
  8941.    ─┴─             Files area              ─┴─
  8942.    ─┬─                                     ─┬─
  8943.     │                                       │
  8944.     └───────────────────────────────────────┘
  8945.  
  8946.    Figure 10-1.  Map of a typical OS/2 logical volume. The boot sector
  8947.    (logical sector 0) contains the OEM identification, BIOS parameter block
  8948.    (BPB), and disk bootstrap. An optional reserved area of variable size is
  8949.    followed by one or more copies of the file allocation table, the root
  8950.    directory, and the files area.
  8951.  
  8952.  Boot Sector
  8953.  
  8954.    Logical sector zero is known as the boot sector and contains all the
  8955.    critical information about a volume's characteristics (Figures 10-2,
  8956.    10-3, and 10-4 on the following pages). The first byte in the sector is
  8957.    always an 80x86 jump instruction──either a normal intrasegment JMP (opcode
  8958.    0E9H) followed by a 16-bit displacement or a "short jump" (opcode 0EBH)
  8959.    followed by an 8-bit displacement and an NOP instruction (opcode 90H). If
  8960.    one of these two JMP opcodes is not present, the disk has not been
  8961.    formatted or was not formatted for use with OS/2 or MS-DOS. Of course, the
  8962.    presence of the JMP opcode is not in itself an assurance that the disk
  8963.    contains an OS/2-compatible file system.
  8964.  
  8965.    00H  ┌──────────────────────────────────┐
  8966.         │       E9 XX XX or EB XX 90       │
  8967.    03H  ├──────────────────────────────────┤
  8968.         │                                  │
  8969.         │       OEM name and version       │
  8970.         │                                  │
  8971.    0BH  ├──────────────────────────────────┤───┐
  8972.         │         Bytes per sector         │   │
  8973.    0DH  ├──────────────────────────────────┤   │
  8974.         │       Sectors per cluster        │   │
  8975.    0EH  ├──────────────────────────────────┤   │
  8976.         │ Reserved sectors, starting at 0  │   │
  8977.    10H  ├──────────────────────────────────┤   │
  8978.         │          Number of FATs          │   │
  8979.    11H  ├──────────────────────────────────┤   │
  8980.         │ Number of root directory entries │   │
  8981.    13H  ├──────────────────────────────────┤  BIOS
  8982.         │ Total sectors in logical volume  │  parameter
  8983.    15H  ├──────────────────────────────────┤  block
  8984.         │      Medium descriptor byte      │   │
  8985.    16H  ├──────────────────────────────────┤   │
  8986.         │         Sectors per FAT          │   │
  8987.    18H  ├──────────────────────────────────┤   │
  8988.         │        Sectors per track         │   │
  8989.    1AH  ├──────────────────────────────────┤   │
  8990.         │         Number of heads          │   │
  8991.    1CH  ├──────────────────────────────────┤   │
  8992.         │     Number of hidden sectors     │   │
  8993.    20H  ├──────────────────────────────────┤   │
  8994.         │ Total sectors in logical volume  │   │
  8995.         │       (if volume > 32 MB)        │   │
  8996.    24H  ├──────────────────────────────────┤───┘
  8997.         │      Physical drive number       │
  8998.    25H  ├──────────────────────────────────┤
  8999.         │             Reserved             │
  9000.    26H  ├──────────────────────────────────┤
  9001.         │  Extended boot record signature  │
  9002.    27H  ├──────────────────────────────────┤
  9003.         │     32-bit binary volume ID      │
  9004.    2BH  ├──────────────────────────────────┤
  9005.        ─┴─                                ─┴─
  9006.        ─┬─           Bootstrap            ─┬─
  9007.         └──────────────────────────────────┘
  9008.  
  9009.    Figure 10-2.  Map of the boot sector of an OS/2 disk. Note JMP at offset
  9010.    zero, OEM identification field, BIOS parameter block, 32-bit binary volume
  9011.    ID, and disk bootstrap code.
  9012.  
  9013.    ──────────────────────────────────────────────────────────────────────────
  9014.           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
  9015.    0000  EB 29 90 49 42 4D 20 31 30 2E 31 00 02 04 01 00  k).IBM 10.1.....
  9016.    0010  02 00 02 EE FF F8 40 00 11 00 05 00 11 00 00 00  ...n.x@.........
  9017.    0020  00 00 00 00 80 00 28 15 E4 A5 23 33 C0 8E D0 BC  ......(.d%#3@.P<
  9018.    0030  00 7C BB C0 07 8E DB A0 10 00 F7 26 16 00 03 06  .|;@..[ ..w&....
  9019.          .
  9020.          .
  9021.          .
  9022.    01C0  72 65 73 74 61 72 74 0D 0A 74 68 65 20 73 79 73  restart..the sys
  9023.    01D0  74 65 6D 2E 0D 0A 00 4F 53 32 4C 44 52 20 20 20  tem....OS2LDR
  9024.    01E0  20 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ..............
  9025.    01F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA  ..............U*
  9026.    ──────────────────────────────────────────────────────────────────────────
  9027.  
  9028.    Figure 10-3.  Partial dump of an OS/2 version 1.1 fixed disk boot sector.
  9029.    See Figures 10-2 and 10-4.
  9030.  
  9031.    ──────────────────────────────────────────────────────────────────────────
  9032.            jmp     $+39            ; jump to bootstrap
  9033.            nop
  9034.  
  9035.            db      'IBM 10.1'      ; OEM identification
  9036.  
  9037.                                    ; BIOS parameter block...
  9038.            dw      512             ; bytes per sector
  9039.            db      4               ; sectors per cluster
  9040.            dw      1               ; reserved sectors
  9041.            db      2               ; number of FATs
  9042.            dw      512             ; root directory entries
  9043.            dw      65518           ; total sectors
  9044.            db      0f8h            ; medium descriptor byte
  9045.            dw      64              ; sectors per FAT
  9046.            dw      17              ; sectors per track
  9047.            dw      5               ; number of heads
  9048.            dd      17              ; hidden sectors
  9049.            dd      0               ; large number of sectors
  9050.            .
  9051.            .
  9052.            .
  9053.    ──────────────────────────────────────────────────────────────────────────
  9054.  
  9055.    Figure 10-4.  Partial disassembly of the boot sector shown in Figure
  9056.    10-3.
  9057.  
  9058.    Following the initial JMP instruction, eight bytes are reserved for the
  9059.    OEM identification field. This field contains the name of the OS/2 OEM
  9060.    (the computer manufacturer who adapted the OS/2 system for a particular
  9061.    hardware configuration) and an OEM internal version number as an ASCII
  9062.    string.
  9063.  
  9064.    The third component of the boot sector is the BIOS parameter block (BPB).
  9065.    This structure describes the physical characteristics of the disk and
  9066.    allows the device driver to calculate the proper physical disk address for
  9067.    a given logical sector number; it also contains information that OS/2 and
  9068.    various system utilities use to calculate the size and location of the
  9069.    FAT, the root directory, and the files area. The BPB is followed by a
  9070.    32-bit binary volume ID, generated by the FORMAT utility, which is
  9071.    different for each volume.
  9072.  
  9073.    The remainder of the boot sector contains the disk bootstrap program,
  9074.    which is the destination of the JMP instruction at the beginning of the
  9075.    sector. The ROM bootstrap reads the disk bootstrap into memory when the
  9076.    system is turned on or restarted; the disk bootstrap in turn reads the
  9077.    file OS2LDR into memory and transfers control to it. The system
  9078.    initialization process is described in detail in Chapter 2.
  9079.  
  9080.  Reserved Area
  9081.  
  9082.    The boot sector is actually part of a reserved area that comprises one or
  9083.    more sectors. The size of the reserved area is described by the reserved
  9084.    sectors field of the BIOS parameter block, at offset 0EH in the boot
  9085.    sector. Remember that the number in the BPB field includes the boot sector
  9086.    itself; so if it contains the value 1 (as it does on most floppy disk
  9087.    types), the length of the reserved area, as shown in Figure 10-1 on p.
  9088.    196, is actually 0 sectors.
  9089.  
  9090.  File Allocation Table
  9091.  
  9092.    When a file is created or extended, disk sectors are assigned to it from
  9093.    the files area in powers of 2 known as allocation units or clusters. The
  9094.    number of sectors per cluster for a given medium is defined in the BIOS
  9095.    parameter block and can be found at offset 0DH in the disk's boot sector.
  9096.    For example, the following table shows the cluster sizes for some common
  9097.    media:
  9098.  
  9099.    Disk Type                 Power of 2       Sec/Cluster
  9100.    ──────────────────────────────────────────────────────────────────────────
  9101.    180 KB floppy             0                1
  9102.    360 KB floppy             1                2
  9103.    1.2 MB floppy             2                4
  9104.    Fixed disk                2, 3...          4, 8...
  9105.    ──────────────────────────────────────────────────────────────────────────
  9106.  
  9107.    The FAT is divided into fields that correspond directly to the assignable
  9108.    clusters on the disk. These fields are either 12 bits or 16 bits,
  9109.    depending on the size of the medium (12 bits if the disk contains fewer
  9110.    than 4087 clusters, 16 bits otherwise).
  9111.  
  9112.    The first two entries in a FAT are always reserved. On IBM-compatible
  9113.    media, the first eight bits of the first FAT entry contain a copy of the
  9114.    medium descriptor byte (Figure 10-5), which is also found in the boot
  9115.    sector's BPB.
  9116.  
  9117.    The second, third, and (if applicable) fourth bytes, which constitute the
  9118.    remainder of the first two reserved FAT fields, always contain 0FFH. The
  9119.    currently defined IBM-format medium descriptor bytes are as follows:
  9120.  
  9121.    Descriptor Byte               Medium Characteristics
  9122.    ──────────────────────────────────────────────────────────────────────────
  9123.    Value              Size              Sides              Sectors
  9124.    0F0H               3.5"              2                  18
  9125.    0F8H               Fixed disk
  9126.    0F9H               5.25"             2                  15
  9127.                       or 3.5"           2                   9
  9128.    0FCH               5.25"             1                   9
  9129.    0FDH               5.25"             2                   9
  9130.    0FEH               5.25"             1                   8
  9131.    0FFH               5.25"             2                   8
  9132.    ──────────────────────────────────────────────────────────────────────────
  9133.  
  9134.    Aside from the first two reserved entries of the FAT, the remainder of the
  9135.    entries describe the usage of their corresponding disk clusters. The
  9136.    contents of the FAT fields are interpreted as follows:
  9137.  
  9138.    Value                     Meaning
  9139.    ──────────────────────────────────────────────────────────────────────────
  9140.    (0)000H                   The cluster is available
  9141.    (F)FF0H─(F)FF6H           Reserved cluster
  9142.    (F)FF7H                   Bad cluster if not part of chain
  9143.    (F)FF8H─(F)FFFH           Last cluster of a file
  9144.    (X)XXXH                   Next cluster in the file
  9145.    ──────────────────────────────────────────────────────────────────────────
  9146.  
  9147.    ──────────────────────────────────────────────────────────────────────────
  9148.           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
  9149.    0000  F9 FF FF 03 F0 FF FF 6F 00 07 80 00 09 A0 00 0B
  9150.    0010  C0 00 FF EF 00 0F 00 01 11 20 01 13 40 01 15 60
  9151.    0020  01 17 80 01 19 A0 01 1B F0 FF 1D E0 01 1F 00 02
  9152.          .
  9153.          .
  9154.          .
  9155.    ──────────────────────────────────────────────────────────────────────────
  9156.  
  9157.    Figure 10-5.  Dump of the first block of the file allocation table for an
  9158.    OS/2 version 1.1 floppy disk. Notice that the first byte of the FAT
  9159.    contains the medium descriptor byte for a 5.25-inch, double-sided,
  9160.    15-sector (1.2 MB) floppy disk.
  9161.  
  9162.    Each file's entry in a disk directory contains the number of the first
  9163.    cluster assigned to that file, which is used as an entry point into the
  9164.    FAT. From the entry point on, each FAT slot contains the cluster number of
  9165.    the next cluster in the file, until a last cluster mark is encountered.
  9166.  
  9167.    At the computer manufacturer's option, a volume can contain two or more
  9168.    identical copies of the FAT. OS/2 simultaneously updates all copies
  9169.    whenever space in the files area is allocated or released. If a FAT sector
  9170.    becomes unreadable, the other copies are tried until a successful read is
  9171.    obtained or until all copies are exhausted. As part of its procedure for
  9172.    checking the integrity of a disk, the CHKDSK program compares the multiple
  9173.    copies (usually two) of the FAT to verify that they are both readable and
  9174.    consistent.
  9175.  
  9176.  Root Directory
  9177.  
  9178.    A volume's root directory immediately follows the FAT(s). It contains
  9179.    32-byte entries that describe files, other directories, and the optional
  9180.    volume label (Figures 10-6 and 10-7). An entry beginning with the byte
  9181.    E5H is available for reuse; it represents a file or directory that has
  9182.    been erased. An entry beginning with a null (zero) byte indicates the
  9183.    logical end of directory; that entry and all subsequent entries are
  9184.    unused.
  9185.  
  9186.    The root directory has a number of special properties. Its size and
  9187.    position are fixed; they are determined by the FORMAT program when a disk
  9188.    is initialized. The number of entries in the root directory and its
  9189.    location on the disk can be obtained from the BPB in the boot sector.
  9190.  
  9191.                                        Attribute
  9192.    Filename field     Extension field  byte      Reserved
  9193.                │                    │     │        │
  9194.           0  1 │2  3  4  5  6  7  8 │9  A │B  C  D │E  F
  9195.    0000  4F 53 32 4C 44 52 20 20 20 20 20 22 00 00 00 00  OS2LDR     "....
  9196.    0010  00 00 00 00 00 00 02 77 4C 11 02 00 00 10 00 00  .......wL.......
  9197.    0020  4F 53 32│4B 52 4E 4C│20 20│20 20│22 00 00│00 00  OS2KRNL    "....
  9198.    0030  00 00 00│00 00 00 58│76 4C│11 04│00 25 A7│04 00  ......XvL...%...
  9199.    0040  42 4F 4F│54 44 49 53│4B 20│20 20│08 00 00│00 00  BOOTDISK   (....
  9200.    0050  00 00 00│00 00 00 A1│00 21│00 00│00│00 00│00 00  ........!.......
  9201.          .       │           │     │     │  │     │
  9202.          .       │           │     │Starting│     │
  9203.          .       │           │     │cluster │     │
  9204.                  │           │     │field   │     │
  9205.           Reserved  Time field  Date        │     File size field
  9206.                                 field       │
  9207.                                             Attribute
  9208.                                             byte for
  9209.                                             volume
  9210.                                             label entry
  9211.  
  9212.    Figure 10-6.  Partial hex dump of the first sector of the root directory
  9213.    for an OS/2 version 1.1 disk. The directory entries for the two system
  9214.    files (OS2LDR and OS2KRNL) and a volume label (BOOTDISK) are shown.
  9215.  
  9216.    00H  ┌─────────────────────────────────┐
  9217.         ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
  9218.         │                                 │
  9219.         │             Filename            │
  9220.         │                                 │
  9221.    08H  ├─────────────────────────────────┤
  9222.         │                                 │
  9223.         │            Extension            │
  9224.         │                                 │
  9225.    0BH  ├─────────────────────────────────┤
  9226.         │          File attribute         │
  9227.    0CH  ├─────────────────────────────────┤
  9228.         │                                 │
  9229.         │             Reserved            │
  9230.         │                                 │
  9231.    16H  ├─────────────────────────────────┤
  9232.         │  Time created or last updated   │
  9233.    18H  ├─────────────────────────────────┤
  9234.         │  Date created or last updated   │
  9235.    1CH  ├─────────────────────────────────┤
  9236.         │             File size           │
  9237.    20H  └─────────────────────────────────┘
  9238.  
  9239.    Figure 10-7.  Format of a single entry in a disk directory. Total length
  9240.    32 bytes (20H bytes). The values that are explained in the filename field
  9241.    refer only to the first character in that field.
  9242.  
  9243.  Files Area
  9244.  
  9245.    The remainder of the volume after the root directory is known as the files
  9246.    area, or data area. The disk sectors in this area are viewed as a pool of
  9247.    clusters, each containing one or more logical sectors, depending on the
  9248.    disk format. Each cluster has a corresponding entry in the FAT that
  9249.    describes its current usage: available, reserved, assigned to a file, or
  9250.    unusable (due to medium defects). Because the first two fields of the FAT
  9251.    are reserved, the first cluster in the files area is assigned the number
  9252.    2.
  9253.  
  9254.    When a file is extended, the FAT is searched from the most recently
  9255.    allocated cluster until a free cluster (designated by a zero FAT field) is
  9256.    found, and that FAT field is changed to a last cluster mark. If the file
  9257.    was previously empty──had no clusters assigned to it──the number of the
  9258.    newly assigned cluster is inserted into the directory entry for the file.
  9259.    Otherwise, the FAT entry of the previous last cluster for the file is
  9260.    updated to point to the new last cluster.
  9261.  
  9262.    Directories other than the root directory are simply a special type of
  9263.    file. Their storage is allocated from the files area, and their contents
  9264.    are 32-byte entries──in the same format as those used in the root
  9265.    directory──that describe files or other directories. Directory entries
  9266.    that describe other directories contain an attribute byte with bit 4 set,
  9267.    zero in the file length field, and the date and time that the directory
  9268.    was created (Figure 10-8 on the following page). The first cluster field,
  9269.    of course, points to the first cluster in the files area that belongs to
  9270.    the directory. (The directory's other clusters can be found only by
  9271.    tracing through the FAT.)
  9272.  
  9273.    All directories except for the root directory contain two special entries
  9274.    with the names . and .. that are put in place when a directory is created.
  9275.    These entries cannot be deleted. The . entry refers to the current
  9276.    directory; its cluster field points to the cluster in which it is found.
  9277.    The .. entry refers to the directory's "parent" (the directory immediately
  9278.    above it in the tree structure), and its cluster field points to the first
  9279.    cluster of the parent. If the parent is the root directory, the cluster
  9280.    field of the .. entry contains zero (Figure 10-9 on the following page).
  9281.  
  9282.         Subdirectory name       Attribute byte
  9283.                         │         indicating a   Reserved
  9284.          .              │   subdirectory entry      │
  9285.          .              │                 │         │
  9286.          .              │                 │         │
  9287.    0080  4D 59 44 49 52 20 20 20 20 20 20 10 00 00 00 00  MYDIR      .....
  9288.    0090  00 00 00 00 00 00 87 9A 9B 0A 2A 00 00 00 00 00  ..........*.....
  9289.          .       │           │     │     │          │
  9290.          .       │           │     │    Starting    │
  9291.          .       │    Time sub-    │    cluster of  │
  9292.                  │    directory    │    subdirectory│
  9293.           Reserved    created      │    file        │
  9294.                                 Date sub-           File size indicated
  9295.                                 directory           as zero bytes
  9296.                                 created
  9297.  
  9298.    Figure 10-8.  Extract from the root directory of an OS/2 disk, showing the
  9299.    entry for a subdirectory named MYDIR. The entry has bit 4 in the attribute
  9300.    byte set, and the cluster field points to the first cluster of the
  9301.    subdirectory file. The date and time stamps are valid, but the file length
  9302.    is zero.
  9303.  
  9304.          Special entry for          Attribute bytes indicating
  9305.          current subdirectory       a subdirectory
  9306.                          │                 │
  9307.          0  1  2  3  4  5│ 6  7  8  9  A  B│ C  D  E  F
  9308.    0000  2E 20 20 20 20 20 20 20 20 20 20 10 00 00 00 00  .         .....
  9309.    0010  00 00 00 00 00 00 87 9A 9B 0A 2A 00 00 00 00 00  ..........*.....
  9310.    0020  2E 2E 20 20 20 20 20 20 20 20 20 10 00 00 00 00  ..        .....
  9311.    0030  00 00 00 00 00 00│87 9A 9B 0A 00 00│00 00 00 00  ................
  9312.    0040  4D 59 46 49 4C 45│20 20 44 41 54 20│00 00 00 00  MYFILE  DAT ....
  9313.    0050  00 00 00 00 00 00│98 9A 9B 0A 2B 00│15 00 00 00  ..........+.....
  9314.    0060  00 00 00 00 00 00│00 00 00 00 00 00│00 00 00 00  ................
  9315.    0070  00 00 00 00 00 00│00 00 00 00 00 00│00 00 00 00  ................
  9316.          .                │                 │
  9317.          .    Special entry          Attribute bytes indicating
  9318.          .    for parent directory   a subdirectory
  9319.  
  9320.    Figure 10-9.  Dump of the first block of the directory MYDIR. Note the .
  9321.    and .. entries. This directory contains exactly one file, named
  9322.    MYFILE.DAT.
  9323.  
  9324.  Fixed Disk Partitions
  9325.  
  9326.    Fixed disks have another layer of organization beyond the logical volume
  9327.    structure we discussed earlier. The FDISK utility organizes a fixed disk
  9328.    into one or more partitions consisting of an integral number of cylinders.
  9329.    Each partition can contain an independent file system and, for that
  9330.    matter, a different operating system.
  9331.  
  9332.    The first physical sector on a fixed disk contains the master boot record,
  9333.    which is laid out as follows:
  9334.  
  9335.    Bytes                     Contents
  9336.    ──────────────────────────────────────────────────────────────────────────
  9337.    000H─1BDH                 Reserved
  9338.    1BEH─1CDH                 Partition #1 descriptor
  9339.    1CEH─1DDH                 Partition #2 descriptor
  9340.    1DEH─1EDH                 Partition #3 descriptor
  9341.    1EEH─1FDH                 Partition #4 descriptor
  9342.    1FEH─1FFH                 Signature word (AA55H)
  9343.    ──────────────────────────────────────────────────────────────────────────
  9344.  
  9345.    The partition descriptors in the master boot record define the size,
  9346.    location, and type of each partition:
  9347.  
  9348.    Byte(s)                   Contents
  9349.    ──────────────────────────────────────────────────────────────────────────
  9350.    00H                       Active flag (0 = not bootable, 80H = bootable)
  9351.    01H                       Starting head
  9352.    02H─03H                   Starting sector/cylinder
  9353.    04H                       Partition type
  9354.                              0 = not used
  9355.                              1 = FAT file system, 12-bit FAT entries
  9356.                              4 = FAT file system, 16-bit FAT entries
  9357.                              5 = extended OS/2 or MS-DOS partition
  9358.                              6 = huge (>32 MB) FAT file system
  9359.    05H                       Ending head
  9360.    06H─07H                   Ending sector/cylinder
  9361.    08H─0BH                   Starting sector for partition, relative to
  9362.                              beginning of disk
  9363.    0CH─0FH                   Partition length in sectors
  9364.    ──────────────────────────────────────────────────────────────────────────
  9365.  
  9366.    The active flag, which indicates that the partition is bootable, can be
  9367.    set on only one partition at a time. A hex dump of a typical master boot
  9368.    record is shown in Figure 10-10 on the following page.
  9369.  
  9370.    OS/2 treats partition types 1, 4, and 6 as normal logical volumes and
  9371.    assigns them their own drive identifiers during the system boot process.
  9372.    Partition type 5 can contain multiple logical volumes and has a special
  9373.    extended boot record that describes each volume. All OS/2 or MS-DOS
  9374.    partitions are initialized with the FORMAT utility, which creates the file
  9375.    system within the partition (boot record, file allocation table(s), root
  9376.    directory, and files area) and optionally places a bootable copy of the
  9377.    operating system in the file system.
  9378.  
  9379.    0000   .  Partition 1                    Active (bootable)
  9380.           .  type byte                      partition flag
  9381.           .      │                                │
  9382.    0180  00 00 00│00 00 00 00 00 00 00 00 00 00 00│00 00
  9383.    0190  00 00 00│00 00 00 00 00 00 00 00 00 00 00│00 00
  9384.    01A0  00 00 00│00 00 00 00 00 00 00 00 00 00 00│00 00
  9385.    01B0  00 00 00│00 00 00 00 00 00 00 00 00 00 00 80 01──1st partition entry
  9386.    01C0  01 00 04 04 D1 02 11 00 00 00 EE FF 00 00 00 00──2nd partition entry
  9387.    01D0  C1 04 05 04 D1 FD 54 00 01 00 02 53 00 00 00 00──3rd partition entry
  9388.    01E0  00 00 00│00 00 00 00 00 00 00 00 00 00 00 00 00──4th partition entry
  9389.    01F0  00 00 00│00 00 00 00 00 00 00 00 00 00 00 55 AA
  9390.                  │                                 │
  9391.              Partition 2                           │
  9392.              type byte                Signature word
  9393.  
  9394.    Figure 10-10.  A partial hex dump of a master block from a fixed disk.
  9395.    This disk contains two partitions. The first partition entry (starting at
  9396.    offset 01BEH) has a 16-bit FAT and is marked "active" to indicate that it
  9397.    contains a bootable copy of OS/2. The second partition entry (starting at
  9398.    offset 01CEH) is an "extended" partition. The third and fourth partition
  9399.    entries are not used in this example.
  9400.  
  9401.  
  9402.  Direct Disk Access
  9403.  
  9404.    OS/2 provides mechanisms for application programs to bypass the file
  9405.    system and to read and write block devices at the logical sector level.
  9406.    These mechanisms let you write low level disk utilities (such as file
  9407.    recovery and repair programs) in a hardware-independent fashion.
  9408.  
  9409.    The API functions that a program uses for direct disk access are the
  9410.    already familiar ones: DosOpen, DosChgFilePtr, DosRead, DosWrite,
  9411.    DosClose, and DosDevIOCtl. Instead of a filename, DosOpen is provided with
  9412.    an ASCIIZ drive identifier, and the "DASD" flag (bit 15) in the OpenMode
  9413.    parameter is set. If the drive exists, DosOpen returns a handle (as
  9414.    usual), but this handle refers to the entire logical volume──represented
  9415.    as though it were a single file.
  9416.  
  9417.    After you obtain a handle, use it with DosDevIOCtl Category 8 Function 00H
  9418.    to "lock" the logical volume so that no other processes can use it during
  9419.    the direct disk access operations. The lock operation fails if any files
  9420.    on the volume are currently open. (This means that you cannot lock the
  9421.    boot drive because several dynlink libraries on that drive are always
  9422.    open.) After the lock succeeds, any subsequent requests to open a file on
  9423.    that volume will return an error.
  9424.  
  9425.    Once the volume is locked, you can use DosChgFilePtr, DosRead, and
  9426.    DosWrite to move to any location and read or write data. It is most
  9427.    convenient to seek, read, and write in multiples of 512 bytes because the
  9428.    physical disk sector size on IBM-compatible media is always 512 bytes, but
  9429.    OS/2 does not constrain you to use this sector size.
  9430.  
  9431.    When direct disk access operations are completed, unlock the logical
  9432.    volume with DosDevIOCtl Category 8 Function 01H; then release the logical
  9433.    volume handle in the usual fashion with DosClose.
  9434.  
  9435.    Figure 10-11 contains sample code that opens and locks logical drive E
  9436.    for direct access, reads the drive's boot sector into a buffer, and then
  9437.    unlocks the drive and releases the handle.
  9438.  
  9439.    ──────────────────────────────────────────────────────────────────────────
  9440.    secsize equ     512             ; physical sector size
  9441.  
  9442.    dname   db      'E:',0          ; logical drive identifier
  9443.    dhandle dw      ?               ; receives drive handle
  9444.    daction dw      ?               ; receives DosOpen action
  9445.  
  9446.    rlen    dw      ?               ; actual bytes read
  9447.  
  9448.    bootsec db      secsize dup (?) ; boot sector read here
  9449.  
  9450.    parblk  db      0               ; DosDevIOCtl dummy
  9451.                                    ; parameter block
  9452.            .
  9453.            .
  9454.            .
  9455.                                    ; open drive E for direct access...
  9456.            push    ds              ; address of drive name
  9457.            push    offset DGROUP:dname
  9458.            push    ds              ; receives drive handle
  9459.            push    offset DGROUP:dhandle
  9460.            push    ds              ; receives DosOpen action
  9461.            push    offset DGROUP:daction
  9462.            push    0               ; file allocation (N/A)
  9463.            push    0
  9464.            push    0               ; file attribute (N/A)
  9465.            push    01h             ; action: open if exists,
  9466.                                    ;         fail if doesn't
  9467.            push    8012h           ; mode: DASD, read/write,
  9468.                                    ;       deny-all
  9469.            push    0               ; reserved DWORD 0
  9470.            push    0
  9471.            call    DosOpen         ; transfer to OS/2
  9472.            or      ax,ax           ; was open successful?
  9473.            jnz     error           ; jump if function failed
  9474.  
  9475.                                    ; lock logical drive...
  9476.            push    0               ; data buffer address
  9477.            push    0               ; (not needed)
  9478.            push    ds              ; parameter buffer address
  9479.            push    offset DGROUP:parblk
  9480.            push    0               ; function
  9481.            push    8               ; category
  9482.            push    dhandle         ; drive handle
  9483.            call    DosDevIOCtl     ; transfer to OS/2
  9484.            or      ax,ax           ; was lock successful?
  9485.            jnz     error           ; jump if function failed
  9486.  
  9487.                                    ; now read boot sector...
  9488.            push    dhandle         ; drive handle
  9489.            push    ds              ; buffer address
  9490.            push    offset DGROUP:bootsec
  9491.            push    secsize         ; buffer length
  9492.            push    ds              ; receives actual length
  9493.            push    offset DGROUP:rlen
  9494.            call    DosRead         ; transfer to OS/2
  9495.            or      ax,ax           ; was read successful?
  9496.            jnz     error           ; jump if function failed
  9497.            cmp     rlen,secsize    ; actual = expected size?
  9498.            jnz     error           ; jump if not enough data
  9499.                                    ; unlock logical drive...
  9500.            push    0               ; data buffer address
  9501.            push    0               ; (not needed)
  9502.            push    ds              ; parameter buffer address
  9503.            push    offset DGROUP:parblk
  9504.            push    1               ; function
  9505.            push    8               ; category
  9506.            push    dhandle         ; drive handle
  9507.            call    DosDevIOCtl     ; transfer to OS/2
  9508.            or      ax,ax           ; was unlock successful?
  9509.            jnz     error           ; jump if function failed
  9510.  
  9511.                                    ; close logical drive...
  9512.            push    dhandle         ; drive handle
  9513.            call    DosClose        ; transfer to OS/2
  9514.            or      ax,ax           ; did close succeed?
  9515.            jnz     error           ; jump if function failed
  9516.            .
  9517.            .
  9518.            .
  9519.    ──────────────────────────────────────────────────────────────────────────
  9520.  
  9521.    Figure 10-11.  Sample code for direct disk access. This sequence of calls
  9522.    opens and locks logical drive E for direct access, reads the drive's boot
  9523.    sector into a buffer, and then unlocks the drive and releases the handle.
  9524.  
  9525.  
  9526.  
  9527.  ────────────────────────────────────────────────────────────────────────────
  9528.  SECTION 4  ADVANCED OS/2 TECHNIQUES
  9529.  ────────────────────────────────────────────────────────────────────────────
  9530.  
  9531.  
  9532.  
  9533.  ────────────────────────────────────────────────────────────────────────────
  9534.  Chapter 11  Memory Management
  9535.  
  9536.    RAM is the fundamental resource in an OS/2 system and also the most
  9537.    precious. Part of the RAM is statically allocated for use by vital OS/2
  9538.    modules and data structures and by the DOS compatibility environment (if
  9539.    it is enabled). The remainder is administered by the OS/2 memory manager
  9540.    and can be thought of as a large pool (sometimes called the global heap)
  9541.    from which segments can be allocated and released as necessary.
  9542.  
  9543.    The OS/2 kernel dynamically allocates memory for the following:
  9544.  
  9545.    ■  Swappable operating system modules and data structures
  9546.  
  9547.    ■  The code, data, and stacks of transient processes
  9548.  
  9549.    ■  Objects created by processes but controlled by the operating system,
  9550.       such as system semaphores and pipes
  9551.  
  9552.    OS/2 exports the memory manager services to applications through a number
  9553.    of API calls (Figure 11-1 on the following page). Application programs
  9554.    can use these services to dynamically create and destroy memory segments
  9555.    for buffers, arrays, tables, and other work areas. The memory management
  9556.    functions fall into three categories:
  9557.  
  9558.    ■  Allocation and release of global memory segments
  9559.  
  9560.    ■  Local memory pool management
  9561.  
  9562.    ■  Allocation and release of named, shareable memory segments
  9563.  
  9564.    The first two categories are discussed in this chapter; the last is
  9565.    deferred to Chapter 13 because it falls naturally into the realm of
  9566.    interprocess communication. But first, a brief review of the 80286
  9567.    hardware support for memory protection and virtual memory is in order.
  9568.    (The 80386, when running OS/2 version 1.0 or 1.1, acts exactly like an
  9569.    80286.)
  9570.  
  9571. ╓┌─┌─────────────────────┌───────────────────────────────────────────────────╖
  9572.    OS/2 Function         Description
  9573.    ──────────────────────────────────────────────────────────────────────────
  9574.    Global Memory Management
  9575.    DosAllocSeg          Allocates memory block with maximum size 64 KB
  9576.    DosCreateCSAlias     Returns executable selector for previously allocated
  9577.                          block
  9578.    OS/2 Function         Description
  9579.    ──────────────────────────────────────────────────────────────────────────
  9580.                         block
  9581.    DosFreeSeg           Releases memory block
  9582.    DosLockSeg           Locks discardable segment
  9583.    DosMemAvail          Returns size of largest contiguous block of physical
  9584.                          memory
  9585.    DosReallocSeg        Changes size of previously allocated block
  9586.    DosSizeSeg           Returns size of previously allocated memory block
  9587.    DosUnlockSeg         Unlocks discardable segment
  9588.  
  9589.    Huge Block Management
  9590.    DosAllocHuge         Allocates huge memory block (larger than 64 KB)
  9591.    DosGetHugeShift      Returns incrementing value for selectors of huge
  9592.                          block
  9593.    DosReallocHuge       Changes size of previously allocated huge block
  9594.  
  9595.    Shared Memory Management (see Chapter 13)
  9596.    DosAllocShrSeg       Creates named shareable memory block
  9597.    DosGetSeg            Makes selector from another process addressable
  9598.    DosGetShrSeg         Obtains selector for existing named shared memory
  9599.    OS/2 Function         Description
  9600.    ──────────────────────────────────────────────────────────────────────────
  9601.   DosGetShrSeg         Obtains selector for existing named shared memory
  9602.                          block
  9603.    DosGiveSeg           Creates selector for use by another process
  9604.  
  9605.    Local Memory Management
  9606.    DosSubAlloc          Allocates memory block from local pool
  9607.    DosSubFree           Releases block to local pool
  9608.    DosSubSet            Initializes or resizes local memory pool
  9609.    ──────────────────────────────────────────────────────────────────────────
  9610.  
  9611.  
  9612.    Figure 11-1.  OS/2 memory management functions at a glance.
  9613.  
  9614.  
  9615.  Memory Protection
  9616.  
  9617.    The Intel 80286 microprocessor starts operating in real mode, which is
  9618.    essentially an 8086 emulation mode. In real mode, segment registers
  9619.    contain paragraph addresses, which you can manipulate directly; you can
  9620.    address a maximum of 1 MB of memory. To form a 20-bit physical memory
  9621.    address, the hardware multiplies the value in a segment register by 16 and
  9622.    combines it with a 16-bit offset (Figure 11-2). Operating systems (such
  9623.    as MS-DOS) that run in real mode cannot intervene when a program does not
  9624.    use memory properly, nor can they prevent one program from writing into
  9625.    another's memory space.
  9626.  
  9627.    ┌─────────────────┐
  9628.    │                 │      Segment register *16
  9629.    └─────────────────┘
  9630.              ─┼─
  9631.         ┌─────────────────┐
  9632.         │                 │ 16-bit offset
  9633.         └─────────────────┘
  9634.               ══
  9635.    ┌──────────────────────┐
  9636.    │                      │ 20-bit physical address
  9637.    └──────────────────────┘
  9638.  
  9639.    Figure 11-2.  Generation of physical addresses in real mode.
  9640.  
  9641.    OS/2 runs the 80286 processor in protected mode, which presents the
  9642.    programmer with hardware architecture that is quite different from that of
  9643.    the 8086. In protected mode, the contents of segment registers are logical
  9644.    selectors that contain several bitfields (Figure 11-3), the largest of
  9645.    which is an index into a descriptor table. Each descriptor represents a
  9646.    memory segment and contains the physical base address, length, and segment
  9647.    attributes (executable, read-only, or read/write). The descriptor also
  9648.    specifies the ring, or privilege level, at which a program must be running
  9649.    to access the segment (Figure 11-4 on the following page).
  9650.  
  9651.     F  E  D  C  B  A  9  8  7  6  5  4  3  2  1  0
  9652.    ┌─────────────────────────────────────┬──┬─────┐
  9653.    │                                     │T │ RPL │
  9654.    └──────────────────┬──────────────────┴┬─┴──┬──┘
  9655.                       │                   │    └─ Requested privilege level
  9656.                       │                   │
  9657.                       │                   └────── 0 = GDT
  9658.                       │                           1 = LDT
  9659.                       │
  9660.                       └────────────────────────── Descriptor table index
  9661.  
  9662.    Figure 11-3.  Structure of a protected mode selector. The actual privilege
  9663.    level is the numeric maximum of the RPL (requested privilege level) of the
  9664.    selector and the CPL (current privilege level) of the process.
  9665.  
  9666.    When an instruction references memory in protected mode, a segment
  9667.    register provides a selector and indicates a specific descriptor. The base
  9668.    address in the descriptor is combined with a 16-bit offset to form a
  9669.    24-bit physical memory address (Figure 11-5 on p. 213). Consequently, you
  9670.    must treat a protected mode selector much like a file handle: It is simply
  9671.    a token for a piece of memory. You cannot assume that a selector is unique
  9672.    across processes, and your program should not (with one exception)
  9673.    manipulate selectors arithmetically.
  9674.  
  9675.    0 ┌────────────────┐
  9676.      │                │
  9677.    1 │ Segment length │
  9678.      │                │
  9679.    2 ├────────────────┤
  9680.      │                │
  9681.    3 │     Segment    │
  9682.      │  base address  │
  9683.    4 │                │
  9684.      │                │
  9685.    5 ├────────────────┤
  9686.      │  Access rights │
  9687.    6 ├────────────────┤
  9688.      │                │
  9689.    7 │    Reserved    │
  9690.      │                │
  9691.    8 └────────────────┘
  9692.  
  9693.    Figure 11-4.  Structure of protected mode data and text (executable)
  9694.    descriptors on the Intel 80286. The descriptors for system segments and
  9695.    call gates have a somewhat different format and are beyond the scope of
  9696.    this book.
  9697.  
  9698.    An OS/2 system contains many descriptor tables: a single global descriptor
  9699.    table (GDT) and, for each process, a local descriptor table (LDT). The GDT
  9700.    contains descriptors for all the LDTs as well as for all segments owned or
  9701.    occupied by the operating system; each process's LDT contains descriptors
  9702.    for the segments that are owned or shared by that process. Only the
  9703.    operating system is entrusted with both the privilege level and the
  9704.    knowledge of physical memory layout that are necessary to create and
  9705.    destroy memory segments through manipulation of descriptor tables.
  9706.  
  9707.    The CPU hardware, using the descriptor tables, enforces memory protection
  9708.    between processes. If a process tries to load a segment register with a
  9709.    selector that is not valid for its LDT or for an entry in the GDT that
  9710.    requires a higher privilege level, a hardware trap occurs. (A trap is like
  9711.    a hardware interrupt but is internally generated.) OS/2 receives control,
  9712.    terminates the offending process, and displays a message to the user.
  9713.  
  9714.    ┌──────────────────┐
  9715.    │                  │ Selector in segment register
  9716.    └────────┬─────────┘
  9717.             │               ┌──────────────┐
  9718.             │               │              │
  9719.             │               │              │
  9720.             │               ├──────────────┤
  9721.             └──────────────│              ├──────┐
  9722.                             ├──────────────┤      │
  9723.                             │              │      │
  9724.                             │┌┐┌┐┌┐┌┐┌┐┌┐┌┐│      │
  9725.                             └┘└┘└┘└┘└┘└┘└┘└┘      │
  9726.                             Descriptor table      │
  9727.               ┌───────────────────────────────────┘
  9728.    ┌─────────────────────┐
  9729.    │                      │ 24-bit physical base address
  9730.    └──────────────────────┘
  9731.              ─┼─
  9732.         ┌─────────────────┐
  9733.         │                 │ 16-bit offset
  9734.         └─────────────────┘
  9735.               ══
  9736.    ┌──────────────────────┐
  9737.    │                      │ 24-bit physical address
  9738.    └──────────────────────┘
  9739.  
  9740.    Figure 11-5.  Generation of physical addresses in protected mode.
  9741.  
  9742.  
  9743.  Virtual Memory
  9744.  
  9745.    Virtual memory allows a process to request and own more bytes of RAM than
  9746.    physically exist in the system. Hardware support for memory protection
  9747.    combines with the operation of two OS/2 modules──the virtual memory
  9748.    manager (VMM) and the swapper──to present the illusion to the task that
  9749.    all its segments are simultaneously present and accessible.
  9750.  
  9751.    When a process attempts to allocate additional memory, the VMM tries to
  9752.    satisfy the request by moving segments to coalesce free blocks of physical
  9753.    memory, by throwing away read-only data segments or text segments which
  9754.    were loaded from an executable file on fixed disk, or by tossing out
  9755.    unlocked, discardable data segments. These strategies failing, the VMM
  9756.    invokes the swapper to move one or more segments to a swap file on disk,
  9757.    choosing the segments with an LRU algorithm. The physical memory occupied
  9758.    by each discarded or swapped segment is then freed, and the present bit in
  9759.    the segment's descriptor is cleared to indicate that the memory segment is
  9760.    not resident.
  9761.  
  9762.    When a selector for a discarded or swapped-out segment is used in a memory
  9763.    access, a hardware trap occurs. The VMM gains control and reads the needed
  9764.    segment from the disk into physical memory from the swap file or from the
  9765.    program's original EXE or DLL file, possibly moving segments or writing
  9766.    another segment out to the disk to make room. It then updates the
  9767.    descriptor to point to the new physical address of the reloaded segment,
  9768.    sets the present bit in the descriptor, and restarts the process's
  9769.    instruction that was trying to access the segment.
  9770.  
  9771.    Because the system implements virtual memory, the presence or absence in
  9772.    physical memory of any particular segment is completely transparent to the
  9773.    process that owns it. If a memory address is accessed that is not
  9774.    currently mapped to some piece of physical RAM, its segment is loaded
  9775.    before the access is allowed to complete. Correspondingly, the amount of
  9776.    memory that can be committed by all the processes in the system combined
  9777.    is usually limited only by the amount of physical memory plus the swap
  9778.    space on the disk. (Although other system-wide limits do exist, they would
  9779.    not be encountered during the execution of a normal application.)
  9780.  
  9781.  
  9782.  Global Memory
  9783.  
  9784.    An application program allocates a global memory segment by calling
  9785.    DosAllocSeg with three parameters: a segment size in bytes (0─65,535), the
  9786.    address of a variable to receive a selector, and a word of flags. A
  9787.    segment size of 0 is treated as a special case and results in a
  9788.    65,536-byte segment. The flags word controls whether the allocated segment
  9789.    can be discarded in low memory situations or shared with other processes
  9790.    (Figure 11-6). Discardable segments are discussed further below, and
  9791.    shared memory segments are covered in Chapter 13; for the moment, note
  9792.    that a flags word of zero is appropriate for most circumstances.
  9793.  
  9794.     F  E  D  C  B  A  9  8  7  6  5  4  3  2  1  0
  9795.    ┌─────────────────────────────────────┬──┬──┬──┐
  9796.    │                                     │  │  │  │
  9797.    └──────────────────┬──────────────────┴┬─┴┬─┴┬─┘
  9798.                       │                   │  │  │
  9799.                       │                   │  │  └─ 1 = shareable with
  9800.                       │                   │  │         DosGiveSeg
  9801.                       │                   │  │
  9802.                       │                   │  └──── 1 = shareable with
  9803.                       │                   │            DosGetSeg
  9804.                       │                   │
  9805.                       │                   └─────── 1 = discardable
  9806.                       │
  9807.                       └─────────────────────────── Reserved
  9808.  
  9809.    Figure 11-6.  Flags word for DosAllocSeg and DosAllocHuge.
  9810.  
  9811.    If segment motion or segment swapping (or both) has not been disabled with
  9812.    the MEMMAN directive in the CONFIG.SYS file, segments can be written to
  9813.    disk or shuffled in memory (or both) to satisfy the allocation request. In
  9814.    such situations, the execution time of a DosAllocSeg call can vary widely
  9815.    because the delay involved in segment swapping or movement depends on many
  9816.    factors (such as the location of the disk read/write head and the size of
  9817.    the segment).
  9818.  
  9819.    A DosAllocSeg call fails if an invalid value is supplied for the flags
  9820.    word, if all physical memory is committed and swapping is disabled, or if
  9821.    all physical memory is committed and swapping is enabled but the disk
  9822.    holding the swap file is full. DosAllocSeg can also fail if the VMM's or
  9823.    swapper's table space is exhausted or if the per-process limit on the
  9824.    number of shared or unshared segments is reached none of these should be
  9825.    limiting factors for typical applications.
  9826.  
  9827.    After the system allocates a segment, the owning process can resize it
  9828.    with the function DosReallocSeg. The function is called with the selector
  9829.    and the new desired size in bytes (0─65,535, with 0 interpreted as
  9830.    65,536). As in the case of DosAllocSeg, if other segments must be moved or
  9831.    swapped to satisfy the request, the time required for the call is
  9832.    unpredictable. A DosReallocSeg call fails if the selector is not valid or
  9833.    if the program that invokes it is attempting to enlarge a segment while
  9834.    the physical memory and disk swap space are exhausted.
  9835.  
  9836.    A process can release a memory segment allocated with DosAllocSeg by
  9837.    passing its selector to DosFreeSeg or by terminating. DosFreeSeg removes
  9838.    the descriptor for the segment from the process's LDT, so any subsequent
  9839.    accesses with the selector result in a GP fault. If the segment is
  9840.    unshared, the physical memory that it occupies is returned to the global
  9841.    pool; if the segment is shared, it is not destroyed until all processes
  9842.    that have access to it have released it or terminated.
  9843.  
  9844.    Figure 11-7 contains an example of allocating, resizing, and releasing a
  9845.    global memory block.
  9846.  
  9847.    ──────────────────────────────────────────────────────────────────────────
  9848.    sel     dw      ?               ; receives selector
  9849.            .
  9850.            .
  9851.            .
  9852.                                    ; allocate global segment...
  9853.            push    4000h           ; initially get 16 KB
  9854.            push    ds              ; receives selector
  9855.            push    offset DGROUP:sel
  9856.            push    0               ; 0 = not shareable
  9857.                                    ;     or discardable
  9858.            call    DosAllocSeg     ; transfer to OS/2
  9859.            or      ax,ax           ; was allocation successful?
  9860.            jnz     error           ; jump if function failed
  9861.            .
  9862.            .
  9863.            .
  9864.                                    ; now resize segment
  9865.                                    ; to 65,536 bytes...
  9866.            push    0               ; 0 indicates 64 KB
  9867.            push    sel             ; segment selector
  9868.            call    DosReallocSeg   ; transfer to OS/2
  9869.            or      ax,ax           ; was resize successful?
  9870.            jnz     error           ; jump if function failed
  9871.            .
  9872.            .
  9873.            .
  9874.                                    ; fill block with -1s...
  9875.            mov     cx,8000h        ; words to initialize
  9876.            mov     es,sel          ; load segment selector
  9877.            xor     di,di           ; start at offset zero
  9878.            mov     ax,-1           ; load initializing value
  9879.            cld                     ; safety first
  9880.            rep stosw               ; fill the block
  9881.            .
  9882.            .
  9883.            .
  9884.                                    ; now release the block...
  9885.            push    sel             ; segment selector
  9886.            call    DosFreeSeg      ; transfer to OS/2
  9887.            or      ax,ax           ; was release successful?
  9888.            jnz     error           ; jump if function failed
  9889.            .
  9890.            .
  9891.            .
  9892.    ──────────────────────────────────────────────────────────────────────────
  9893.  
  9894.    Figure 11-7.  Allocating a 16 KB memory block with DosAllocSeg, resizing
  9895.    it to 64 KB with DosReallocSeg, filling it with -1s, and then releasing it
  9896.    with DosFreeSeg.
  9897.  
  9898.  
  9899.  Huge Memory Blocks
  9900.  
  9901.    As we saw earlier, protected mode memory addresses are generated by
  9902.    combining a selector with a 16-bit offset. This means that the largest
  9903.    continuously addressable, physically contiguous chunk of memory a process
  9904.    can own is 65,536 bytes. For applications that need larger data
  9905.    structures, OS/2 offers a set of "huge memory block" functions to manage
  9906.    areas of logically contiguous storage spanning multiple segments.
  9907.  
  9908.    A huge block is initially allocated with the function DosAllocHuge. Two of
  9909.    the parameters for this function are the same as those for DosAllocSeg:
  9910.    the flags word controlling discardability and sharing, and the address of
  9911.    a variable to receive a selector. The other parameters are the number of
  9912.    full 64 KB segments to allocate, the length in bytes (which may be zero)
  9913.    of an additional partial segment, and the maximum size (in 64 KB segments)
  9914.    to which the process can resize the block at some later time.
  9915.  
  9916.    If the DosAllocHuge call succeeds, the component segments of the huge
  9917.    block are assigned predictable, sequential selectors. The memory within
  9918.    the block can therefore be smoothly addressed by incrementing the offset
  9919.    until a segment is exhausted and then performing special arithmetic on the
  9920.    selector, using an incrementing value supplied by the operating system to
  9921.    move to the next segment. The incrementing value is obtained with the
  9922.    function DosGetHugeShift, which returns a shift count to be applied to the
  9923.    value 1.
  9924.  
  9925.    For example, if DosGetHugeShift returns 4, shifting the value 1 left by
  9926.    four bit positions yields the increment 10H (16). If DosAllocHuge was
  9927.    called to allocate 224 KB of memory and returned the base selector 017FH
  9928.    for the huge memory block, the entire area would be addressed as follows:
  9929.  
  9930.    Block Range              Selector                Offsets
  9931.    ──────────────────────────────────────────────────────────────────────────
  9932.    0─63 KB                  017FH                   0─FFFFH
  9933.    64─127 KB                018FH                   0─FFFFH
  9934.    128─191 KB               019FH                   0─FFFFH
  9935.    192─223 KB               01AFH                   0─7FFFH
  9936.    ──────────────────────────────────────────────────────────────────────────
  9937.  
  9938.    DosGetHugeShift need be called only once and can be called at any time
  9939.    during the execution of the application. The value it returns does not
  9940.    change while the application is running. The shift count can also be
  9941.    obtained from the global information segment (see DosGetInfoSeg) or from a
  9942.    special loadtime dynlink fixup for the symbol Huge_Shift. Addressing of
  9943.    huge blocks must be performed with care to avoid generating invalid
  9944.    selectors, wrapping a segment, or overrunning a partial last segment; such
  9945.    errors cause a GP fault that terminates the application. Figure 11-8
  9946.    contains a simple example of the address arithmetic that would be
  9947.    necessary in an actual program.
  9948.  
  9949.    ──────────────────────────────────────────────────────────────────────────
  9950.    nsegs   equ     3               ; number of complete
  9951.                                    ; segments in huge block
  9952.  
  9953.    nbytes  equ     8000h           ; number of bytes in
  9954.                                    ; partial last segment
  9955.  
  9956.    nmax    equ     4               ; maximum size of huge
  9957.                                    ; block in segments
  9958.  
  9959.    sel     dw      ?               ; receives base selector
  9960.                                    ; for huge block
  9961.  
  9962.    shift   dw      ?               ; receives shift count
  9963.                                    ; for selector increment
  9964.  
  9965.            .
  9966.            .
  9967.            .
  9968.                                    ; get selector increment...
  9969.            push    ds              ; receives shift count
  9970.            push    offset DGROUP:shift
  9971.            call    DosGetHugeShift ; transfer to OS/2
  9972.            or      ax,ax           ; did we get shift count?
  9973.            jnz     error           ; jump if function failed
  9974.            .
  9975.            .
  9976.            .
  9977.                                    ; now allocate huge block...
  9978.            push    nsegs           ; number of complete segments
  9979.            push    nbytes          ; bytes in last segment
  9980.            push    ds              ; receives base selector
  9981.            push    offset DGROUP:sel
  9982.            push    nmax            ; potential max segments
  9983.            push    0               ; 0 = segment not shared
  9984.            call    DosAllocHuge    ; transfer to OS/2
  9985.            or      ax,ax           ; was allocation successful?
  9986.            jnz     error           ; jump if function failed
  9987.            .
  9988.            .
  9989.            .
  9990.                                    ; convert shift count to
  9991.                                    ; actual increment...
  9992.            mov     bx,1            ; 1 will be shifted left
  9993.            mov     cx,shift        ; count from DosGetHugeShift
  9994.            shl     bx,cl           ; BX <- selector increment
  9995.  
  9996.            mov     es,sel          ; load base selector
  9997.            mov     dx,nsegs        ; total segment count
  9998.            mov     ax,-1           ; load initializing value
  9999.            cld                     ; safety first
  10000.  
  10001.    label1:                         ; initialize a segment...
  10002.            mov     cx,8000h        ; words per segment
  10003.            xor     di,di           ; start at offset zero
  10004.            rep stosw               ; fill the segment
  10005.  
  10006.            mov     cx,es           ; increment selector
  10007.            add     cx,bx           ; for next segment
  10008.            mov     es,cx
  10009.            dec     dx              ; another segment?
  10010.            jnz     label1          ; yes, loop
  10011.  
  10012.                                    ; fill partial segment...
  10013.            mov     cx,nbytes       ; length of partial segment
  10014.            xor     di,di           ; start at offset zero
  10015.            rep stosb               ; fill the segment
  10016.  
  10017.            .
  10018.            .                       ; other processing here
  10019.            .
  10020.  
  10021.                                    ; release the huge block...
  10022.            push    sel             ; base selector for block
  10023.            call    DosFreeSeg      ; transfer to OS/2
  10024.            or      ax,ax           ; was release successful?
  10025.            jnz     error           ; jump if function failed
  10026.            .
  10027.            .
  10028.            .
  10029.    ──────────────────────────────────────────────────────────────────────────
  10030.  
  10031.    Figure 11-8.  Allocating a 224 KB huge memory block with DosAllocHuge,
  10032.    filling it with -1s, and then releasing it.
  10033.  
  10034.    To change the size of a huge memory block after it is allocated, use
  10035.    DosReallocHuge. This function is called with the base selector obtained
  10036.    from DosAllocHuge, the new number of 64 KB segments, and the length in
  10037.    bytes of any partial last segment. You can always reduce the size of a
  10038.    huge block, but you cannot increase its size beyond the maximum specified
  10039.    when the block was created. Such an attempt fails because OS/2 was not
  10040.    warned to reserve a sufficient number of consecutive selectors. Of course,
  10041.    a "grow" operation might also fail for the various other reasons described
  10042.    above for DosReallocSeg.
  10043.  
  10044.    A process can release a huge memory block by terminating or by calling
  10045.    DosFreeSeg with the base selector. All segments of a huge block are
  10046.    deallocated at the same time.
  10047.  
  10048.  
  10049.  Discardable Segments
  10050.  
  10051.    A discardable segment is created by setting bit 2 of the flags parameter
  10052.    for a call to DosAllocSeg or DosAllocHuge. Discardable segments are useful
  10053.    for tables and other data that can be regenerated quickly on demand. Such
  10054.    a segment might also be employed for infrequently used static data that is
  10055.    loaded from a file──the overhead of writing the data to a swap file and
  10056.    finding swap space (to duplicate data that already exists in the original
  10057.    file) is thereby avoided.
  10058.  
  10059.    When a segment is allocated with the discardable bit set in the flags
  10060.    word, OS/2 initially considers the segment to be swappable and movable,
  10061.    but not discardable. The program notifies OS/2 that the segment is
  10062.    discardable by calling DosUnlockSeg with the selector for the segment.
  10063.    Subsequently, when a low memory situation arises, OS/2 throws away any
  10064.    unlocked, discardable segments on an LRU basis before it resorts to
  10065.    swapping nondiscardable segments. In the case of a huge memory block, all
  10066.    the segments that comprise the block are discarded together.
  10067.  
  10068.    When a program needs to access an unlocked, discardable segment, it first
  10069.    calls DosLockSeg with the segment's selector (or the base selector, in the
  10070.    case of a huge block). If the contents of the segment are still valid, the
  10071.    DosLockSeg call is successful (AX = 0); if the segment has been discarded,
  10072.    an error code is returned. In the latter case, the process must reallocate
  10073.    the segment to its original size with DosReallocSeg or DosReallocHuge
  10074.    (which also perform an implicit DosLockSeg) and then rebuild or reload the
  10075.    contents of the segment. Figure 11-9 contains an example of this process.
  10076.  
  10077.    DosLockSeg and DosUnlockSeg calls can be nested. If DosLockSeg is called
  10078.    multiple times to lock a segment, DosUnlockSeg must be called the same
  10079.    number of times before the segment becomes discardable again. OS/2
  10080.    maintains only an 8-bit lock counter for each segment; if the count
  10081.    reaches 255 for a segment, the segment becomes permanently locked and
  10082.    subsequent calls to DosLockSeg or DosUnlockSeg have no effect.
  10083.  
  10084.    ──────────────────────────────────────────────────────────────────────────
  10085.    sel     dw      ?               ; receives selector
  10086.  
  10087.            .
  10088.            .
  10089.            .
  10090.                                    ; allocate discardable global segment...
  10091.            push    8000h           ; segment is 32 KB long
  10092.            push    ds              ; variable receives selector
  10093.            push    offset DGROUP:sel
  10094.            push    4               ; 4 = discardable,
  10095.                                    ;     not shareable
  10096.            call    DosAllocSeg     ; transfer to OS/2
  10097.            or      ax,ax           ; was allocation successful?
  10098.            jnz     error           ; jump if function failed
  10099.  
  10100.            call    rtable          ; read the data table into the segment
  10101.  
  10102.                                    ; now unlock segment...
  10103.            push    sel             ; selector from DosAllocSeg
  10104.            call    DosUnlockSeg    ; transfer to OS/2
  10105.            or      ax,ax           ; was unlock successful?
  10106.            jnz     error           ; jump if function failed
  10107.  
  10108.            .
  10109.            .                       ; other processing here...
  10110.            .
  10111.  
  10112.                                    ; now lock segment so we can use data table
  10113.            push    sel             ; selector from DosAllocSeg
  10114.            call    DosLockSeg      ; transfer to OS/2
  10115.            or      ax,ax           ; was lock successful?
  10116.            jz      label1          ; jump if segment locked
  10117.  
  10118.                                    ; segment was discarded, must reallocate it
  10119.            push    8000h           ; size is 32 KB
  10120.            push    sel             ; selector for segment
  10121.            call    DosReallocSeg   ; transfer to OS/2
  10122.            or      ax,ax           ; was resize successful?
  10123.            jnz     error           ; jump if function failed
  10124.  
  10125.            call    rtable          ; refresh segment by rereading data table
  10126.    label1:                         ; segment is now locked...
  10127.  
  10128.            .
  10129.            .                       ; use contents of segment...
  10130.            .
  10131.  
  10132.                                    ; unlock segment again...
  10133.            push    sel             ; selector from DosAllocSeg
  10134.            call    DosUnlockSeg    ; transfer to OS/2
  10135.            or      ax,ax           ; was unlock successful?
  10136.            jnz     error           ; jump if function failed
  10137.            .
  10138.            .
  10139.            .
  10140.    ──────────────────────────────────────────────────────────────────────────
  10141.  
  10142.    Figure 11-9.  Allocating a discardable segment to hold a table of data,
  10143.    unlocking the segment so that it can be discarded in low memory
  10144.    situations, locking the segment when its contents are needed, and
  10145.    reallocating and refreshing the segment if it was discarded. Assume that
  10146.    the subroutine rtable (not shown) reads the table of data from a disk file
  10147.    into the memory block whose selector is in the variable sel.
  10148.  
  10149.  
  10150.  Writable Code Segments
  10151.  
  10152.    On occasion, a program might want to load or dynamically create executable
  10153.    code in a read/write data segment and then branch to that code.
  10154.    Unfortunately, because a particular selector can refer to either
  10155.    executable code or data but not both, loading a selector for a data
  10156.    segment into the CS register by execution of a far JMP or CALL results in
  10157.    a GP fault and process termination.
  10158.  
  10159.    The solution is to allocate two selectors for the same piece of physical
  10160.    memory: a read/write data selector and an executable (text) selector. The
  10161.    function DosCreateCSAlias exists specifically for this purpose: It returns
  10162.    an executable "alias" for a data segment currently owned by a process.
  10163.    This function makes it easy to write incremental compilers and other
  10164.    self-extending systems for OS/2.
  10165.  
  10166.    A basic strategy to use for incremental compilers is as follows:
  10167.  
  10168.    1.  Allocate a nonshared, nondiscardable data segment that is large enough
  10169.        to hold the program's original machine code and any additional machine
  10170.        code that will be compiled during program execution.
  10171.  
  10172.    2.  Copy the current contents of the text segment to the new data segment.
  10173.  
  10174.    3.  Obtain a CS alias for the new data segment and transfer control to the
  10175.        new segment.
  10176.  
  10177.    4.  Release the original text segment and continue execution from the new
  10178.        segment, which has both text and read/write data selectors.
  10179.  
  10180.    Figure 11-10 contains skeleton code for these four steps.
  10181.  
  10182.    Programs which need to execute the generated code only once can use other
  10183.    approaches. For example, some Presentation Manager graphics modules build
  10184.    machine code on the stack, obtain a CS alias for the stack segment, call
  10185.    the new code, and then discard the code immediately.
  10186.  
  10187.    ──────────────────────────────────────────────────────────────────────────
  10188.    dsel    dw      ?                ; receives data selector
  10189.  
  10190.    xoffs   dw      offset label1    ; offset of jump target
  10191.    xsel    dw      ?                ; executable selector
  10192.  
  10193.    tsel    dw      seg _TEXT        ; selector for original
  10194.                                     ; executable (text) segment
  10195.  
  10196.            .
  10197.            .
  10198.            .
  10199.                                     ; allocate global segment...
  10200.            push    0                ; segment size = 64 KB
  10201.            push    ds               ; variable receives selector
  10202.            push    offset DGROUP:dsel
  10203.            push    0                ; 0 = not shareable
  10204.                                     ;     or discardable
  10205.            call    DosAllocSeg      ; transfer to OS/2
  10206.            or      ax,ax            ; was allocation successful?
  10207.            jnz     error            ; jump if function failed
  10208.  
  10209.                                     ; get executable alias...
  10210.            push    dsel             ; selector from DosAllocSeg
  10211.            push    ds               ; receives executable alias
  10212.            push    offset DGROUP:xsel
  10213.            call    DosCreateCSAlias; transfer to OS/2
  10214.            or      ax,ax            ; was function successful?
  10215.            jnz     error            ; jump if no alias available
  10216.  
  10217.                                     ; copy old text segment
  10218.                                     ; to new segment...
  10219.            mov     es,dsel          ; read/write selector
  10220.            mov     cx,pgmlen        ; length of program
  10221.            xor     si,si            ; initialize 'from' offset
  10222.            xor     di,di            ; initialize 'to' offset
  10223.                                    ; move the program code
  10224.            rep movs byte ptr es:[di],byte ptr cs:[si]
  10225.  
  10226.            jmp     dword ptr xoffs ; transfer control to
  10227.                                    ; the new text segment
  10228.  
  10229.    label1:                         ; now we are executing from
  10230.                                    ; the new text segment...
  10231.                                    ; release old text segment
  10232.            push    tsel            ; segment selector
  10233.            call    DosFreeSeg      ; transfer to OS/2
  10234.            or      ax,ax           ; was release successful?
  10235.            jnz     error           ; jump if function failed
  10236.            .
  10237.            .
  10238.            .
  10239.  
  10240.    pgmlen  equ     $               ; get length of text segment
  10241.                                    ; at end of program
  10242.    ──────────────────────────────────────────────────────────────────────────
  10243.  
  10244.    Figure 11-10.  Allocating a new data segment, obtaining a CS alias for
  10245.    that segment, and continuing execution from the new segment.
  10246.  
  10247.  
  10248.  Local Storage
  10249.  
  10250.    Dynamic allocation of local storage is a standard feature of C function
  10251.    libraries; the portion of a program's private memory set aside for this
  10252.    purpose is called the local heap. C application programs can dynamically
  10253.    allocate, resize, and release small blocks from the local heap for arrays
  10254.    and buffers with the library functions malloc(), realloc(), and free(),
  10255.    respectively. This technique allows much more efficient use of memory than
  10256.    statically preallocating all the data structures that a program might
  10257.    require only transiently during its execution.
  10258.  
  10259.    OS/2 supports three functions that manage local memory pools (analogous to
  10260.    the routines provided by the C runtime library) on behalf of programs
  10261.    written in assembler or any high level language. You can use these
  10262.    functions with any read/write data segment except for DGROUP to which the
  10263.    program has access──shared or unshared.
  10264.  
  10265.    The function DosSubSet initializes the OS/2 control structures for a local
  10266.    memory pool. The calling parameters are a segment selector obtained from
  10267.    DosAllocSeg or DosAllocShrSeg, a flag (1 to initialize the pool), and the
  10268.    heap size in bytes (not necessarily the entire segment). The heap size is
  10269.    always a multiple of 4 bytes, with a minimum of 12 bytes; if the size is
  10270.    not evenly divisible by 4, the system rounds it up. Note that DosSubSet
  10271.    considers a size of zero to be an error, although it is treated as a
  10272.    special case (equivalent to 65,536 bytes) by most other memory functions.
  10273.  
  10274.    The function DosSubAlloc obtains a block of storage from a local memory
  10275.    pool. It is called with the selector of the memory segment that holds the
  10276.    local pool, the desired size in bytes, and the address of a variable to
  10277.    receive the offset of the block. The maximum size block you can allocate
  10278.    is the size of the pool itself less 8 bytes (the length of the OS/2
  10279.    control structure at the head of the segment). The call to DosSubAlloc
  10280.    fails if the local pool contains insufficient free memory to satisfy the
  10281.    request; in this event, the actual size of the largest available block is
  10282.    returned instead.
  10283.  
  10284.    To release a block back to a local pool, call DosSubFree with the selector
  10285.    of the segment holding the pool, and the offset and size of the block. The
  10286.    function fails if the offset and size parameters do not match those of a
  10287.    block previously allocated with DosSubAlloc.
  10288.  
  10289.    OS/2 does not provide a function analogous to DosReallocSeg that you can
  10290.    use to resize a block of storage obtained with DosSubAlloc. A program can
  10291.    simulate such a function by calling DosSubAlloc to obtain a new block of
  10292.    the desired size, copying the data from the old block into the new block,
  10293.    and releasing the original block. To enlarge the local pool as a whole,
  10294.    make an additional call to DosSubSet and pass it the same selector as in
  10295.    the original call, the new size in bytes, and a flag value of 0. If the
  10296.    local pool already occupies the entire segment, the segment itself must
  10297.    first be expanded with DosReallocSeg. Of course, if the segment size is
  10298.    already 65,536 bytes, neither it nor the size of the local pool can
  10299.    increase further.
  10300.  
  10301.    ──────────────────────────────────────────────────────────────────────────
  10302.    WARNING:
  10303.      To improve their execution speed, DosSubAlloc and DosSubFree perform
  10304.      minimal error checking. Invalid parameters are not always detected and
  10305.      can produce unpredictable program behavior or a GP fault that terminates
  10306.      the process.
  10307.    ──────────────────────────────────────────────────────────────────────────
  10308.  
  10309.  
  10310.  
  10311.  ────────────────────────────────────────────────────────────────────────────
  10312.  Chapter 12  Multitasking
  10313.  
  10314.    Multitasking is the technique of dividing CPU time among programs in such
  10315.    a way that they appear to be running simultaneously. Of course, the
  10316.    processor is executing only one sequence of machine instructions within
  10317.    one task at any given time, but the process of switching from one task to
  10318.    another is invisible to both the user and the programs themselves. (Please
  10319.    accept the imprecise word task for a few paragraphs; specifics will
  10320.    follow.) The operating system allocates memory resources to the various
  10321.    tasks and resolves contending requests for peripheral devices such as
  10322.    video displays and disk drives.
  10323.  
  10324.    The part of the operating system that allocates CPU time among tasks is
  10325.    called the scheduler, and the rotation from one task to another is called
  10326.    a context switch. When a context switch occurs, the scheduler must save
  10327.    the current state of the active task, restore the registers and program
  10328.    counter to their state when the imminent task was last suspended, and then
  10329.    transfer control to that task.
  10330.  
  10331.    Multitasking operating systems use two basic types of schedulers:
  10332.    event-driven and preemptive. Event-driven schedulers rely on each task to
  10333.    be well-behaved──to yield control of the processor at frequent enough
  10334.    intervals so that every program has acceptable throughput, and none is
  10335.    starved for CPU cycles. This yielding of control can be explicit (calling
  10336.    a specific operating system function to give up control) or implicit
  10337.    (suspending the program when it requests the operating system to perform
  10338.    I/O on its behalf and regaining control only after the I/O is completed
  10339.    and other tasks have in turn yielded control). The event-driven strategy
  10340.    is efficient in transaction-oriented systems, which perform a great deal
  10341.    of I/O and not much computation, but such a system can be brought to its
  10342.    knees by a single compute-bound task such as an in-memory sort.
  10343.  
  10344.    A preemptive scheduler relies on an external signal generated at regular
  10345.    intervals, typically a hardware interrupt triggered by a programmable
  10346.    timer or clock device. When the interrupt occurs, the operating system
  10347.    gains control from whatever task was executing, saves its context,
  10348.    evaluates the list of programs that are ready to run, and gives control to
  10349.    ("dispatches") the next program. This approach to multitasking is often
  10350.    called "time-slicing," a term based on the mental image of dividing the
  10351.    sweep of a second hand around a clock face into little wedges and doling
  10352.    them out to the eligible programs.
  10353.  
  10354.    The central processors of mainframes and minicomputers──and of
  10355.    microcomputers based on the more powerful microprocessors such as the
  10356.    Intel 80286/386 and the Motorola 68020/30──include features that make
  10357.    multitasking operating systems more efficient and robust. The two most
  10358.    important of these features are privilege levels and memory protection.
  10359.  
  10360.    In the simplest use of privilege levels, the CPU is in either kernel mode
  10361.    or user mode at any given time. In kernel mode, which is reserved for the
  10362.    operating system, any machine instruction can be executed. As part of the
  10363.    mechanism for transferring control from the operating system to an
  10364.    application program, the CPU is switched into user mode. In this CPU
  10365.    state, execution of certain reserved instructions (such as those that
  10366.    disable interrupts or write to an I/O port) causes a CPU fault and returns
  10367.    control to the operating system.
  10368.  
  10369.    Memory protection, as described in Chapter 11, is the hardware's ability
  10370.    to detect any attempt by an application program to access memory that does
  10371.    not belong to it. Such an access generates a CPU fault, allowing the
  10372.    operating system to regain control and put the wayward task to sleep
  10373.    forever.
  10374.  
  10375.  
  10376.  Multitasking in OS/2
  10377.  
  10378.    OS/2 is built around a preemptive, priority-based scheduler. To understand
  10379.    multitasking under OS/2, you need to grasp three distinct but related
  10380.    operating system concepts: processes, sessions, and threads (Figure
  10381.    12-1). The simplest kind of OS/2 process is very similar to an
  10382.    application program loaded for execution under MS-DOS. OS/2 creates a
  10383.    process by allocating memory segments to hold the code, data, and stack
  10384.    for the process and by initializing the memory segments from the contents
  10385.    of the program's EXE disk file.
  10386.  
  10387.    Once it is running, a process can access additional system resources, such
  10388.    as files, pipes, semaphores, queues, and additional memory, with various
  10389.    OS/2 function calls (Figure 12-2 on p. 230). It can start child
  10390.    processes, influence their fate, find out when and why they terminate, and
  10391.    retrieve their exit codes.
  10392.  
  10393.                               ╔═════════╗   Physical Screen,
  10394.                               ║ Session ║   Keyboard, and
  10395.                               ║    1    ╟─┐ Mouse
  10396.                               ╚═════════╝ └┐
  10397.                             ┌─────────────┐└┐
  10398.                             │             │┌┴┐
  10399.                             └─────────────┘└─┘
  10400.                                    ║ Task switcher
  10401.               ╔════════════════════╬─────────//─────────┐
  10402.          ╔═════════╗          ╔═════════╗          ╔═════════╗
  10403.          ║ Session ║          ║ Session ║    ...   ║ Session ║
  10404.          ║    1    ╟─┐        ║    2    ╟─┐        ║    n    ╟─┐
  10405.          ╚═════════╝ └┐       ╚═════════╝ └┐       ╚═════════╝ └┐
  10406.        ┌─────────────┐└┐    ┌─────────────┐└┐    ┌─────────────┐└┐
  10407.        │             │┌┴┐   │             │┌┴┐   │             │┌┴┐
  10408.        └─────────────┘└─┘   └─────────────┘└─┘   └─────────────┘└─┘
  10409.        ┌────┘    └──┐               │             ┌────┘    └──┐
  10410.    Process A    Process B       Process C     Process D    Process E
  10411.    ┌───────┐    ┌───────┐     ┌───────────┐   ┌───────┐    ┌───────┐
  10412.    │ T   T │    │   T   │     │ T   T   T │   │   T   │    │ T   T │
  10413.    │ h   h │    │   h   │     │ h   h   h │   │   h   │    │ h   h │
  10414.    │ r   r │    │   r   │     │ r   r   r │   │   r   │    │ r   r │
  10415.    │ e   e │    │   e   │     │ e   e   e │   │   e   │    │ e   e │
  10416.    │ a   a │    │   a   │     │ a   a   a │   │   a   │    │ a   a │
  10417.    │ d   d │    │   d   │     │ d   d   d │   │   d   │    │ d   d │
  10418.    │       │    │       │     │           │   │       │    │       │
  10419.    │ 1   2 │    │   1   │     │ 1   2   3 │   │   1   │    │ 1   2 │
  10420.    └───────┘    └───────┘     └───────────┘   └───────┘    └───────┘
  10421.  
  10422.    Figure 12-1.  The elements of multitasking: sessions, processes, and
  10423.    threads.
  10424.  
  10425.    Processes in turn are members of sessions (sometimes called screen
  10426.    groups), which are the average user's perception of OS/2 multitasking. By
  10427.    pressing Ctrl-Esc, the user can exit from the current session to a window
  10428.    displayed by the Task Manager and then select an application already
  10429.    executing in another session or establish a new session with the Program
  10430.    Starter. The user can also cycle directly between sessions by pressing
  10431.    Alt-Esc.
  10432.  
  10433.    OS/2 maintains a separate virtual screen, mouse, and keyboard for each
  10434.    session. The virtual screen receives the output of all processes in the
  10435.    session and is mapped to the physical display whenever the user selects
  10436.    that session using the Task Manager. New processes are added to an
  10437.    existing session when they are launched by a process already executing
  10438.    within the group. The Task Manager can juggle as many as 16 sessions;
  10439.    however, several are dedicated to special processes, such as the swapper
  10440.    and critical error handler, leaving 12 protected mode sessions and 1 real
  10441.    mode session available for applications.
  10442.  
  10443.                               Process
  10444.    ┌────────────────────────────────────────────────────────────┐
  10445.    │             Process ID (PID)                               │
  10446.    │             Local descriptor table (LDT)                   │
  10447.    │             System resources owned or used by process      │
  10448.    │               Files                                        │
  10449.    │               Pipes                                        │
  10450.    │               Queues                                       │
  10451.    │               System semaphores                            │
  10452.    │               Device monitors                              │
  10453.    │             Child sessions and processes                   │
  10454.    │             Current disk                                   │
  10455.    │             Current directory for each disk                │
  10456.    │             Exception and signal handlers                  │
  10457.    │                                                            │
  10458.    │     Thread 1           Thread 2              Thread n      │
  10459.    │ ┌───────────────┐  ┌───────────────┐     ┌───────────────┐ │
  10460.    │ │ Thread ID = 1 │  │ Thread ID = 2 │     │ Thread ID = n │ │
  10461.    │ │ Priority      │  │ Priority      │     │ Priority      │ │
  10462.    │ │ Thread state  │  │ Thread state  │     │ Thread state  │ │
  10463.    │ │  Blocked      │  │  Blocked      │     │  Blocked      │ │
  10464.    │ │  Ready        │  │  Ready        │ ... │  Ready        │ │
  10465.    │ │  Executing    │  │  Executing    │     │  Executing    │ │
  10466.    │ │ Length of     │  │ Length of     │     │ Length of     │ │
  10467.    │ │  timeslice    │  │  timeslice    │     │  timeslice    │ │
  10468.    │ │ CPU state     │  │ CPU state     │     │ CPU state     │ │
  10469.    │ │ 80x87 state   │  │ 80x87 state   │     │ 80x87 state   │ │
  10470.    │ └───────────────┘  └───────────────┘     └───────────────┘ │
  10471.    └────────────────────────────────────────────────────────────┘
  10472.  
  10473.    Figure 12-2.  Process-specific and thread-specific information maintained
  10474.    by OS/2.
  10475.  
  10476.    The OS/2 scheduler, however, knows nothing about processes or sessions; it
  10477.    distributes the CPU cycles among dispatchable entities known as threads.
  10478.    Each thread has its own priority, stack, point of execution, CPU state,
  10479.    and (optionally) numeric coprocessor state (Figure 12-2 again). The state
  10480.    includes the CPU's flags and the contents of all registers. At any given
  10481.    time, a thread is either blocked (waiting for I/O or some other event),
  10482.    ready to execute, or actually executing. The scheduler can handle a
  10483.    maximum of 255 threads, although the default system-wide limit (controlled
  10484.    by the THREADS directive in CONFIG.SYS) is 64 in OS/2 version 1.0 and 128
  10485.    in OS/2 version 1.1.
  10486.  
  10487.    Each process starts life with a primary thread (also called thread 1),
  10488.    whose execution begins at the entry point designated in the EXE file
  10489.    header. However, that first thread can start additional threads within the
  10490.    same process, all of which share ownership of the process's resources.
  10491.    Multiple threads within a process execute asynchronously to one another,
  10492.    can have different priorities, and can manipulate one another's
  10493.    priorities.
  10494.  
  10495.    Although the threads within a process have separate stacks, they share the
  10496.    same near data segment (DGROUP) and thus the same local heap. Careful
  10497.    design of your code to use the stack for local variables makes procedures
  10498.    inherently shareable among threads. (This occurs naturally in C programs.)
  10499.    Access to static variables or other data structures must be coordinated
  10500.    between threads through use of semaphores or other synchronization
  10501.    mechanisms (see Chapter 13).
  10502.  
  10503.    How does control of the CPU pass from one thread to another? A clock tick
  10504.    or other hardware interrupt causes a transition to kernel mode, whereupon
  10505.    OS/2 saves the current thread's state and calls the appropriate driver to
  10506.    handle the interrupt. Certain API calls by a thread also result in a
  10507.    switch to kernel mode.
  10508.  
  10509.    When OS/2 is ready to exit kernel mode, the scheduler examines its list of
  10510.    active threads. The thread with the highest priority that is ready to
  10511.    execute gains control of the machine. If the thread that was most recently
  10512.    active is one of multiple eligible threads with the same priority, and if
  10513.    it has not used up its timeslice, it receives preference. If a thread
  10514.    becomes starved for CPU cycles because other threads with higher
  10515.    priorities are getting all the attention, OS/2 temporarily bumps that
  10516.    thread's priority to a higher value (see below: "Configuring the OS/2
  10517.    Multitasker"). A thread that is currently reading the keyboard also
  10518.    receives a supplemental boost to its priority.
  10519.  
  10520.  
  10521.  The Multitasking API
  10522.  
  10523.    The OS/2 multitasking services available to application programs are
  10524.    summarized in Figure 12-3 on the following page. These services include:
  10525.  
  10526.    ■  Starting and stopping child processes
  10527.  
  10528.    ■  Obtaining the termination type and exit code of a child process
  10529.  
  10530.    ■  Starting, suspending, and destroying threads
  10531.  
  10532.    ■  Altering the priorities of threads
  10533.  
  10534.    ■  Starting, stopping, and selecting new sessions
  10535.  
  10536.    This range of services allows tremendous flexibility in application
  10537.    design. An application can be factored into asynchronously executing
  10538.    components in many different ways: as multiple threads in a single process
  10539.    sharing the same files and memory; as multiple processes sharing the same
  10540.    virtual screen, keyboard, and pointing device; as multiple processes with
  10541.    distinct virtual screens, keyboard, and pointing devices──or any
  10542.    combination!
  10543.  
  10544. ╓┌─┌────────────────────┌────────────────────────────────────────────────────╖
  10545.    OS/2 Function        Description
  10546.    ──────────────────────────────────────────────────────────────────────────
  10547.    Thread Management
  10548.    DosCreateThread     Creates new thread of execution within same process
  10549.    DosEnterCritSec     Freezes other threads in same process
  10550.    DosExit             Terminates current thread
  10551.    DosExitCritSec      Unfreezes other threads in same process
  10552.    DosGetPrty          Gets thread priority
  10553.    DosResumeThread     Reactivates specific thread
  10554.    DosSetPrty          Sets thread priority
  10555.    DosSuspendThread    Suspends specific thread
  10556.  
  10557.    Process Management
  10558.    DosCwait            Checks child process status
  10559.    DosExecPgm          Runs child process
  10560.    DosExit             Terminates current process
  10561.    DosExitList         Registers routines to be executed at process
  10562.                         termination
  10563.    DosGetPID           Returns PID of current process and its parent
  10564.    DosGetPPID          Returns PID of specified process's parent
  10565.    OS/2 Function        Description
  10566.    ──────────────────────────────────────────────────────────────────────────
  10567.   DosGetPPID          Returns PID of specified process's parent
  10568.    DosKillProcess      Terminates another process
  10569.    DosPTrace           Inspects/modifies/traces child process
  10570.  
  10571.    Session Management
  10572.    DosSelectSession    Brings session to foreground
  10573.    DosSetSession       Sets session status
  10574.    DosStartSession     Creates new session
  10575.    DosStopSession      Terminates session
  10576.    ──────────────────────────────────────────────────────────────────────────
  10577.  
  10578.  
  10579.    Figure 12-3.  OS/2 multitasking services at a glance.
  10580.  
  10581.  Managing Processes
  10582.  
  10583.    The API function DosExecPgm is used by one process, called the parent, to
  10584.    load and execute another process, called the child. This function is
  10585.    analogous to, but considerably more powerful than, the EXEC function (Int
  10586.    21H Function 4BH) in MS-DOS versions 2.0 and later. A child process can,
  10587.    in turn, load other processes until some essential system resource is
  10588.    exhausted.
  10589.  
  10590.    The child process inherits access to some of the parent process's
  10591.    resources: the handles for any open files (unless the parent process
  10592.    explicitly opened the files with the no-inheritance flag), handles to any
  10593.    open pipes, the current disk and current directories of the parent, and a
  10594.    copy of the parent's environment (unless the parent goes to the trouble of
  10595.    creating a new block and passes a pointer to it).
  10596.  
  10597.    DosExecPgm is called with the following:
  10598.  
  10599.    ■  The address of the ASCIIZ program pathname
  10600.  
  10601.    ■  A parameter selecting synchronous, asynchronous, or detached execution
  10602.       for the child process
  10603.  
  10604.    ■  Addresses or NULL pointers for an argument string block and environment
  10605.       to be passed to the child process
  10606.  
  10607.    ■  Addresses of buffers to receive the process ID of the child and certain
  10608.       other information
  10609.  
  10610.    The program pathname must include the extension (typically EXE), but the
  10611.    OS/2 loader looks at the file header to determine whether the file is
  10612.    executable and does not deduce that fact from the extension. If a fully
  10613.    qualified pathname (including a drive, path, and filename) is supplied for
  10614.    the child, the loader looks only for the specified file. If only a
  10615.    filename and extension are supplied, the loader looks in each directory
  10616.    named in the process's environment PATH string for a matching file.
  10617.  
  10618.    When a child process executes synchronously, execution of the thread (in
  10619.    the parent process) that made the DosExecPgm call is suspended until the
  10620.    child process terminates (either intentionally or as the result of an
  10621.    error condition). When the thread in the parent resumes execution, it
  10622.    receives the return code of the child and a termination code that
  10623.    indicates whether the child terminated normally or was terminated by a
  10624.    fault, Ctrl-C, Ctrl-Break, and so forth.
  10625.  
  10626.    If the child process is asynchronous, OS/2 returns the child's process ID
  10627.    (PID), and the thread that issued the DosExecPgm call continues to execute
  10628.    while the child is running. A thread in the parent process can later use
  10629.    DosCwait to find out whether the child is still running or to
  10630.    resynchronize with the child process (by suspending itself until the child
  10631.    terminates and then obtaining the child's return code). Alternatively, the
  10632.    parent can use the child's PID with DosKillProcess to terminate
  10633.    unilaterally the execution of the child process (and, optionally, all
  10634.    grandchild processes) at any time.
  10635.  
  10636.    When a child process is started with the detach option, the process is
  10637.    "orphaned" from the parent: DosKillProcess and DosCwait functions issued
  10638.    by the parent or its ancestors do not take notice of or affect detached
  10639.    child processes. The child cannot interact with the user without using
  10640.    VioPopUp. (The interactive DETACH command is implemented with this
  10641.    variation of DosExecPgm.) The system also provides DosExecPgm options for
  10642.    session managers and debuggers; however, their use is beyond the scope of
  10643.    this book (although they are described briefly in the reference section).
  10644.  
  10645.    The environment block, which consists of a series of ASCIIZ strings
  10646.    terminated by an extra zero byte, was discussed in detail in Chapter 3.
  10647.    The parent process can construct a new environment for the child, or it
  10648.    can pass a NULL pointer, in which case the child receives an exact copy of
  10649.    the parent's environment.
  10650.  
  10651.    The block of argument strings supplied to DosExecPgm is similar in form to
  10652.    the environment block. For compatibility with CMD.EXE, the parent should
  10653.    pass the ASCIIZ simple filename of the child (without extension), then the
  10654.    ASCIIZ command tail, followed by another zero byte. In the case of closely
  10655.    coupled child processes that the user would never run directly, the parent
  10656.    typically passes a NULL pointer and communicates its needs to the child by
  10657.    other methods.
  10658.  
  10659.    A DosExecPgm call can fail for many reasons. The two most common are that
  10660.    the specified file cannot be found or that the drive or file containing
  10661.    the program was locked by another process. In a heavily loaded system, a
  10662.    program might not load because the system limits on threads have been
  10663.    reached or because the disk does not hold sufficient swap space. Another,
  10664.    more obscure reason is that the program references a dynlink library or
  10665.    entry point that the system loader can't find; in that case, the name of
  10666.    the missing library and procedure is returned to the parent.
  10667.  
  10668.    Figures 12-4 and 12-5 contain simple examples of the use of DosExecPgm
  10669.    for synchronous and asynchronous execution of CHKDSK as a child process.
  10670.    The options for asynchronous execution provided by DosExecPgm, coupled
  10671.    with the several options available with DosCwait and DosKillProcess, allow
  10672.    for very flexible execution-time relationships between parent and child
  10673.    processes.
  10674.  
  10675.    ──────────────────────────────────────────────────────────────────────────
  10676.    pname   db      'chkdsk.com',0  ; pathname of child
  10677.  
  10678.                                    ; argument strings...
  10679.    args    db      'chkdsk',0      ; simple filename of child
  10680.            db      ' *.*',0        ; simulated command tail
  10681.            db      0               ; extra null ends block
  10682.  
  10683.                                    ; receives return codes
  10684.    retcode dw      0               ; child's termination type
  10685.            dw      0               ; child's DosExit code
  10686.    objname db      64 dup (0)      ; receives name of dynlink
  10687.    objname_len equ $-objname       ; causing DosExecPgm failure
  10688.  
  10689.            .
  10690.            .
  10691.            .
  10692.                                    ; run CHKDSK as synchronous
  10693.                                    ; child process...
  10694.            push    ds              ; receives module/entry
  10695.                                    ; point if dynlink fails
  10696.            push    offset DGROUP:objname
  10697.            push    objname_len     ; length of buffer
  10698.            push    0               ; 0 = execute synchronously
  10699.            push    ds              ; address of argument block
  10700.            push    offset DGROUP:args
  10701.            push    0               ; address of environment
  10702.            push    0               ; (0 = inherit parent's)
  10703.            push    ds              ; receives child's exit
  10704.                                    ; and termination codes
  10705.            push    offset DGROUP:retcode
  10706.            push    ds              ; pathname of child
  10707.            push    offset DGROUP:pname
  10708.            call    DosExecPgm      ; transfer to OS/2
  10709.            or      ax,ax           ; did child process run?
  10710.            jnz     error           ; jump if function failed
  10711.            .
  10712.            .
  10713.            .
  10714.    ──────────────────────────────────────────────────────────────────────────
  10715.  
  10716.    Figure 12-4.  Using DosExecPgm to run CHKDSK as a synchronous child
  10717.    process.
  10718.  
  10719.    ──────────────────────────────────────────────────────────────────────────
  10720.    pname   db      'chkdsk.com',0  ; pathname of child
  10721.  
  10722.                                    ; argument strings for child
  10723.    args    db      'chkdsk',0      ; simple filename of child
  10724.            db      ' *.*',0        ; simulated command tail
  10725.            db      0               ; extra null ends block
  10726.  
  10727.                                    ; receives DosExecPgm info
  10728.    cpid    dw      0               ; PID of child process
  10729.            dw      0               ; (word not used)
  10730.    scratch dw      0               ; PID scratch for DosCwait
  10731.  
  10732.                                    ; receives DosCwait info
  10733.    retcode dw      0               ; child termination type
  10734.            dw      0               ; child exit code
  10735.  
  10736.    objname db      64 dup (0)      ; receives name of dynlink
  10737.    objname_len equ $-objname       ; causing DosExecPgm failure
  10738.  
  10739.            .
  10740.            .
  10741.            .
  10742.                                    ; run CHKDSK as asynchronous
  10743.                                    ; child process...
  10744.            push    ds              ; receives module/entry
  10745.                                    ; point if dynlink fails
  10746.            push    offset DGROUP:objname
  10747.            push    objname_len     ; length of buffer
  10748.            push    2               ; 2 = execute asynchronously
  10749.            push    ds              ; address of argument block
  10750.            push    offset DGROUP:args
  10751.            push    0               ; address of environment
  10752.            push    0               ; (0 = inherit parent's)
  10753.            push    ds              ; receives child's PID
  10754.            push    offset DGROUP:cpid
  10755.            push    ds              ; name of child program
  10756.            push    offset DGROUP:pname
  10757.            call    DosExecPgm      ; transfer to OS/2
  10758.            or      ax,ax           ; was child process started?
  10759.            jnz     error           ; jump if function failed
  10760.  
  10761.            .
  10762.            .                       ; other processing here...
  10763.            .
  10764.  
  10765.                                    ; now resynchronize with
  10766.                                    ; child process...
  10767.            push    0               ; 0 = immediate child only
  10768.            push    0               ; 0 = wait till child ends
  10769.            push    ds              ; receives termination info
  10770.            push    offset DGROUP:retcode
  10771.            push    ds              ; receives PID (N/A here)
  10772.            push    offset DGROUP:scratch
  10773.            push    cpid            ; PID to wait for
  10774.            call    DosCwait        ; transfer to OS/2
  10775.            .
  10776.            .
  10777.            .
  10778.    ──────────────────────────────────────────────────────────────────────────
  10779.  
  10780.    Figure 12-5.  Using DosExecPgm to run CHKDSK as an asynchronous child
  10781.    process, then calling DosCwait to resynchronize with CHKDSK and retrieve
  10782.    its exit code and termination type.
  10783.  
  10784.  Managing Threads
  10785.  
  10786.    Multiple threads within a process share the memory space and system
  10787.    resources owned by the process and can communicate rapidly using shared
  10788.    data structures. Threads can start, suspend, or die quickly and have low
  10789.    system overhead; they are not visible outside the process and do not
  10790.    require the extensive initialization and cleanup associated with starting
  10791.    or terminating a process. Use of multiple threads is particularly
  10792.    appropriate when a process must manage devices with vastly different I/O
  10793.    rates and remain responsive to the needs of each.
  10794.  
  10795.    A thread starts another thread by calling DosCreateThread and supplying it
  10796.    with the addresses of the initial execution point and stack base of the
  10797.    new thread. (Because the stack grows downward, you must supply the address
  10798.    of the last byte plus 1 in the area allocated for the stack.) If the call
  10799.    to DosCreateThread succeeds, OS/2 returns a thread ID and adds the thread
  10800.    to the scheduler's list. The thread ID is local to the process and, unlike
  10801.    a PID, is not unique within the system.
  10802.  
  10803.    Each thread is initially entered through a far call from OS/2 and can
  10804.    terminate with a far return or by calling DosExit. In the latter case, the
  10805.    thread supplies a parameter that indicates whether the entire process or
  10806.    the thread alone is being terminated. In OS/2 version 1.1, a DosExit call
  10807.    by thread 1 receives special treatment and always terminates the process,
  10808.    whereas in OS/2 1.0 the sole remaining thread in a process need not be
  10809.    thread 1.
  10810.  
  10811.    When a thread has nothing to do, it should block on a semaphore to await
  10812.    reactivation by another thread or process that clears the same semaphore
  10813.    (see Chapter 13). An alternative (and, in most cases, less desirable)
  10814.    approach is for the thread to use DosSleep to block itself for a
  10815.    programmed period of time or simply to give up the remainder of its
  10816.    timeslice. While a thread is blocking, its cost to the system in CPU
  10817.    cycles is nearly zero.
  10818.  
  10819.    Aside from using semaphores, which require cooperation, the threads in a
  10820.    given process can influence one another's execution in several ways. A
  10821.    thread can call DosSuspendThread or DosResumeThread, with the thread ID
  10822.    returned by DosCreateThread, to suspend or reactivate another thread
  10823.    without that thread's knowledge; a thread can protect itself from such
  10824.    interference with DosEnterCritSec and DosExitCritSec. Similarly, a thread
  10825.    can use a thread ID with DosGetPrty or DosSetPrty to inspect or modify its
  10826.    execution priority or that of other threads.
  10827.  
  10828.    Thread priorities fall into three categories──idle-time, regular, and
  10829.    time-critical──with 32 levels within each category. The idle-time category
  10830.    is intended for threads that should execute only when the system has
  10831.    nothing else to do; it should never be used for a thread that interacts
  10832.    with the user. The time-critical category is intended for threads that
  10833.    must respond rapidly to some event (usually timer related or I/O related)
  10834.    to preserve system performance; you will see a use of this category in the
  10835.    SNAP program (Chapter 18). The normal priority category applies to
  10836.    routine execution of threads in application programs, and the primary
  10837.    thread of a process is placed into this category by default; additional
  10838.    threads within a process inherit the priority of the thread that created
  10839.    them.
  10840.  
  10841.    Figure 12-6 contains the source code for a simple multithreaded program.
  10842.    The first thread starts up another thread which emits 10 beeps at 1-second
  10843.    intervals, while the first thread waits for a keystroke and terminates the
  10844.    process when one is received. Although this is a trivial use of threading,
  10845.    it gives an inkling of the enormous power of the thread concept and
  10846.    exhibits the ease with which you can incorporate asynchronous processing
  10847.    into an OS/2 application.
  10848.  
  10849.    ──────────────────────────────────────────────────────────────────────────
  10850.    stksiz  equ     2048            ; size of stack for
  10851.                                    ; new thread
  10852.            .
  10853.            .
  10854.            .
  10855.    cdata   db      10 dup (0)      ; receives character data
  10856.                                    ; from KbdCharIn
  10857.  
  10858.    sel     dw      ?               ; receives selector
  10859.                                    ; from DosAllocSeg
  10860.  
  10861.    tid     dw      ?               ; receives thread ID
  10862.  
  10863.            .
  10864.            .
  10865.            .
  10866.                                    ; allocate stack segment
  10867.                                    ; for new thread...
  10868.            push    stksiz          ; size of new segment
  10869.            push    ds              ; receives selector
  10870.            push    offset DGROUP:sel
  10871.            push    0               ; 0 = segment not shareable
  10872.            call    DosAllocSeg     ; transfer to OS/2
  10873.            or      ax,ax           ; was allocation successful?
  10874.            jnz     error           ; jump if function failed
  10875.  
  10876.                                    ; start new thread...
  10877.            push    cs              ; thread entry point
  10878.            push    offset _TEXT:beeper
  10879.            push    ds              ; receives thread ID
  10880.            push    offset DGROUP:tid
  10881.            push    sel             ; stack for new thread
  10882.            push    stksiz
  10883.            call    DosCreateThread ; transfer to OS/2
  10884.            or      ax,ax           ; was new thread created?
  10885.            jnz     error           ; jump if function failed
  10886.  
  10887.                                    ; now wait for key...
  10888.            push    ds              ; receives data packet
  10889.            push    offset DGROUP:cdata
  10890.            push    0               ; 0 = wait for character
  10891.            push    0               ; keyboard handle
  10892.            call    KbdCharIn       ; transfer to OS/2
  10893.  
  10894.                                    ; final exit to OS/2...
  10895.            push    1               ; terminate all threads
  10896.            push    0               ; exit code = 0 (success)
  10897.            call    DosExit         ; transfer to OS/2
  10898.            .
  10899.            .
  10900.            .
  10901.  
  10902.    beeper  proc    far             ; thread entry point
  10903.  
  10904.            mov     cx,10           ; max of 10 beeps
  10905.  
  10906.    beep1:                          ; sound a tone...
  10907.            push    440             ; 440 Hz
  10908.            push    100             ; 100 milliseconds
  10909.            call    DosBeep         ; transfer to OS/2
  10910.  
  10911.            push    0               ; now suspend thread
  10912.            push    1000            ; for 1 second...
  10913.            call    DosSleep        ; transfer to OS/2
  10914.  
  10915.            loop    beep1           ; loop 10 times
  10916.  
  10917.            ret                     ; terminate this
  10918.                                    ; thread only
  10919.    beeper  endp
  10920.    ──────────────────────────────────────────────────────────────────────────
  10921.  
  10922.    Figure 12-6.  A simple demonstration of multithreading. The first thread
  10923.    starts a second thread and then waits for any key to be pressed. When a
  10924.    key is detected, the process is terminated regardless of the state of the
  10925.    second thread. The second thread emits 10 beeps at 1-second intervals and
  10926.    then destroys itself, possibly still leaving the first thread waiting for
  10927.    the keyboard.
  10928.  
  10929.  Managing Sessions
  10930.  
  10931.    The API functions for session control are present mainly to support the
  10932.    Task Manager, but they can also be useful at the application level in
  10933.    special situations. The most important of these functions,
  10934.    DosStartSession, is a sort of big brother to DosExecPgm: It starts a new
  10935.    process and at the same time creates a child session to hold that process.
  10936.    DosStartSession requires the following parameters:
  10937.  
  10938.    ■  The ASCIIZ pathname for the child process to be placed in the new
  10939.       session
  10940.  
  10941.    ■  An ASCIIZ argument string (command tail) for the child process
  10942.  
  10943.    ■  An ASCIIZ session title for the Task Manager menu
  10944.  
  10945.    ■  Parameters that control the behavior and visibility of the child
  10946.       session and process
  10947.  
  10948.    ■  An optional queue name
  10949.  
  10950.    The pathname must be fully qualified (drive, path, filename, and extension
  10951.    of the process to be loaded in the child session); the use of the session
  10952.    title is obvious. The argument string is a single ASCIIZ string, rather
  10953.    than the block of argument strings used by DosExecPgm, and it is not
  10954.    accompanied by a pointer to an environment for the new session and
  10955.    process. The initial process in a session receives an environment that is
  10956.    empty except for a PATH string; it is expected to synthesize an
  10957.    appropriate environment for its children. The new session also starts with
  10958.    the current drive and directory set to the root directory of the system's
  10959.    boot device, rather than inheriting current settings from the process that
  10960.    calls DosStartSession.
  10961.  
  10962.    If a queue name is supplied, OS/2 places a message in the queue when the
  10963.    child session (actually, the initial process in the child session)
  10964.    terminates. (Queues will be explained in Chapter 13.) The message
  10965.    contains the child session's ID and the child process's return code.
  10966.    DosCwait cannot be used with DosStartSession to synchronize with a process
  10967.    in the new session.
  10968.  
  10969.    The other parameters to DosStartSession determine whether the new session
  10970.    starts in the foreground (visible, replacing the parent session) or in the
  10971.    background (not visible, but available on the Task Manager switch list);
  10972.    whether the initial process in the session is traceable (for use by
  10973.    debuggers); and whether it is related to the parent session. When the
  10974.    child session is not related, it runs free, and the parent cannot further
  10975.    affect it. (Incidentally, the START command of CMD.EXE is implemented as a
  10976.    call to DosStartSession with parameters specifying that the new session is
  10977.    in the background and not related.)
  10978.  
  10979.    When the child session is related, the parent can terminate the session
  10980.    and its member processes (and any grandchild sessions and processes) with
  10981.    DosStopSession. Related child sessions and their descendants are also
  10982.    exposed to a hidden danger: They are unilaterally terminated by OS/2 if
  10983.    the parent exits or is terminated unexpectedly. This relationship between
  10984.    parent and child sessions is much different than the relationship that
  10985.    exists between parent and child processes created by DosExecPgm.
  10986.  
  10987.    A parent can also use two other API functions to control related child
  10988.    sessions. If the parent is executing in the foreground, it can make one of
  10989.    its child sessions visible instead with DosSelectSession (and later return
  10990.    itself to visibility in the same manner). Finally, the parent can use
  10991.    DosSetSession to determine whether a related child session is selectable
  10992.    via the Task Manager or to bind itself to a child session. When a parent
  10993.    and child session are bound together, the child session comes to the
  10994.    foreground whenever the child or parent session is selected.
  10995.  
  10996.    Figure 12-7 contains a simple example of a DosStartSession call. The code
  10997.    launches a related child session that contains a copy of CMD.EXE. The
  10998.    argument string for CMD.EXE causes it to display a directory listing and
  10999.    then a prompt. The child session terminates when the user types EXIT or
  11000.    when the parent session terminates or issues a DosStopSession call.
  11001.  
  11002.    ──────────────────────────────────────────────────────────────────────────
  11003.                                    ; child session parameters
  11004.    sesdata dw      24              ; length of structure
  11005.            dw      1               ; 0 = unrelated, 1 = related
  11006.            dw      1               ; 0 = foreground, 1 = background
  11007.            dw      0               ; 0 = not traceable, 1 = traceable
  11008.            dd      title           ; pointer to session title
  11009.            dd      pname           ; pointer to process name
  11010.            dd      args            ; pointer to argument string
  11011.            dd      0               ; pointer to queue name
  11012.  
  11013.                                    ; pathname for child
  11014.    pname   db      'c:\os2\pbin\cmd.exe',0
  11015.  
  11016.    args    db      ' /k dir/w',0   ; argument string for child
  11017.                                    ; session title
  11018.    title   db      'Child Command Processor',0
  11019.  
  11020.    cid     dw      0               ; receives process ID
  11021.  
  11022.    sid     dw      0               ; receives session ID
  11023.  
  11024.            .
  11025.            .
  11026.            .
  11027.                                    ; run CMD.EXE as child
  11028.                                    ; in a new session...
  11029.            push    ds              ; address of session data
  11030.            push    offset DGROUP:sesdata
  11031.            push    ds              ; receives session ID
  11032.            push    offset DGROUP:sid
  11033.            push    ds              ; receives process ID
  11034.            push    offset DGROUP:cid
  11035.            call    DosStartSession ; transfer to OS/2
  11036.            or      ax,ax           ; was session started?
  11037.            jnz     error           ; jump if function failed
  11038.            .
  11039.            .
  11040.            .
  11041.    ──────────────────────────────────────────────────────────────────────────
  11042.  
  11043.    Figure 12-7.  Using DosStartSession to load CMD.EXE in a new screen group.
  11044.    The command tail passed to CMD.EXE causes it to display a directory
  11045.    listing followed by a prompt. Because the new session is placed in the
  11046.    background, you must switch to it with the Task Manager hot key in order
  11047.    to view the directory.
  11048.  
  11049.  
  11050.  Configuring the OS/2 Multitasker
  11051.  
  11052.    Four optional CONFIG.SYS directives influence OS/2 multitasking:
  11053.  
  11054.      THREADS=n
  11055.      MAXWAIT=seconds
  11056.      PRIORITY=[ABSOLUTE | DYNAMIC]
  11057.      TIMESLICE=x[,y]
  11058.  
  11059.    The THREADS directive controls the number of threads that can be created
  11060.    simultaneously in the system. The parameter n must fall in the range 16
  11061.    through 255, with a default of 64 in OS/2 version 1.0 and 128 in version
  11062.    1.1. The upper limit of 255 cannot be expanded.
  11063.  
  11064.    When a thread is denied the CPU for the number of seconds specified by
  11065.    MAXWAIT (because other higher priority threads are monopolizing the
  11066.    timeslices), the "starved" thread receives a temporary increase in
  11067.    priority for one timeslice. This provision ensures that all processes make
  11068.    some progress and eventually recognize events that concern them. The
  11069.    seconds parameter for MAXWAIT must be in the range 1 through 255, with a
  11070.    default value of 3.
  11071.  
  11072.    The PRIORITY directive enables or disables mechanisms that OS/2 uses to
  11073.    alter the priority of each thread dynamically──based on the activity of
  11074.    other threads within the system. If PRIORITY=DYNAMIC (the default
  11075.    keyword), the priority of threads can be adjusted within a class as a
  11076.    function of their execution history and the visibility of their session.
  11077.    When PRIORITY=ABSOLUTE, thread priorities are not adjusted, and the
  11078.    MAXWAIT directive has no effect.
  11079.  
  11080.    The TIMESLICE directive controls the length of the timeslices that the
  11081.    OS/2 scheduler uses. The x parameter represents the normal length of a
  11082.    thread's timeslice in milliseconds, and y is the maximum length; if
  11083.    TIMESLICE is not present in CONFIG.SYS, both default to 31. If the y
  11084.    parameter is absent, its value defaults to x. If a thread uses up its
  11085.    entire timeslice and must be preempted, its next timeslice is one tick
  11086.    longer, up to the limit set by the y parameter. In this way, OS/2 lets you
  11087.    minimize context-switching overhead when several compute-bound threads are
  11088.    running at the same priority.
  11089.  
  11090.  
  11091.  A Simple Command Interpreter
  11092.  
  11093.    The listings TINYCMD.C (Figure 12-8 on the following page) and
  11094.    TINYCMD.ASM (Figure 12-9 on p. 247) contain the source code for a simple
  11095.    self-contained OS/2 command interpreter. TINYCMD is table-driven so that
  11096.    it can be extended easily.
  11097.  
  11098.    After the user types a command and presses the Enter key, TINYCMD tries to
  11099.    match the first token in the line against its table of internal
  11100.    (intrinsic) commands. This version of TINYCMD has only three intrinsic
  11101.    commands:
  11102.  
  11103.    Command                   Action
  11104.    ──────────────────────────────────────────────────────────────────────────
  11105.    CLS                       Clears the screen
  11106.    EXIT                      Terminates TINYCMD
  11107.    VER                       Displays OS/2 version number
  11108.    ──────────────────────────────────────────────────────────────────────────
  11109.  
  11110.    If a command is not intrinsic, TINYCMD appends .EXE to the first token and
  11111.    attempts to load a program by that name using DosExecPgm. If no program
  11112.    file is found, TINYCMD displays the message Bad command or filename.
  11113.  
  11114.    To add new intrinsic commands to TINYCMD, simply code a routine with the
  11115.    appropriate action, and then add the command text and the name of the
  11116.    associated routine to the table named COMMANDS. You can also prevent the
  11117.    user from running specific extrinsic programs by adding their names to the
  11118.    COMMANDS table and associating them with a routine that displays an error
  11119.    message.
  11120.  
  11121.    To compile TINYCMD.C into the executable file TINYCMD.EXE, type the
  11122.    following command:
  11123.  
  11124.    [C:\] CL TINYCMD.C  <Enter>
  11125.  
  11126.    To assemble TINYCMD.ASM into the file TINYCMD.OBJ, type the following
  11127.    command:
  11128.  
  11129.    [C:\] MASM TINYCMD.ASM;  <Enter>
  11130.  
  11131.    Then use the following command to link TINYCMD.OBJ with the module
  11132.    definition file TINYCMD.DEF (Figure 12-10 on p. 254) and the import
  11133.    library OS2.LIB to produce TINYCMD.EXE:
  11134.  
  11135.    [C:\] LINK TINYCMD,,,OS2,TINYCMD  <Enter>
  11136.  
  11137.    TINYCMD is intended only as a demonstration and would require considerable
  11138.    additional work to approach the functionality of CMD.EXE. You would need
  11139.    to add code to process SET commands and maintain the environment, to
  11140.    interpret batch files, and to search for EXE, COM, and BAT files in that
  11141.    order. You would also need to add a signal handler for Ctrl-C and
  11142.    Ctrl-Break to enable TINYCMD to recover control when the user enters one
  11143.    of these key sequences to terminate a child process. (Signal handlers are
  11144.    discussed in Chapter 13.)
  11145.  
  11146.    ──────────────────────────────────────────────────────────────────────────
  11147.    /*
  11148.            TINYCMD.C
  11149.  
  11150.            A simple command interpreter for OS/2.
  11151.  
  11152.            Compile with:  C> cl tinycmd.c
  11153.            Usage is:  C> tinycmd
  11154.  
  11155.            Copyright (C) 1988 Ray Duncan
  11156.    */
  11157.  
  11158.    #include <stdio.h>
  11159.    #include <string.h>
  11160.    #include <memory.h>
  11161.                                          /* macro to return number of
  11162.                                             elements in a structure */
  11163.    #define DIM(x) (sizeof(x) / sizeof(x[0]))
  11164.    #define INPSIZE 80                    /* maximum input length */
  11165.  
  11166.    #define API unsigned extern far pascal  /* OS/2 API prototypes */
  11167.  
  11168.    API DosExecPgm(char far *, int, int, char far *, char far *,
  11169.                   int far *, char far *);
  11170.    API DosGetVersion(char far *);
  11171.  
  11172.    unsigned intrinsic(char *);           /* local function prototypes */
  11173.    void extrinsic(char *);
  11174.    void cls_cmd(void);
  11175.    void ver_cmd(void);
  11176.    void exit_cmd(void);
  11177.  
  11178.    struct _commands {                    /* intrinsic commands table */
  11179.        char *name;                       /* command name */
  11180.        int  (*fxn)();                    /* command function */
  11181.        }
  11182.          commands[] = { "CLS",   cls_cmd,  /* built-in commands */
  11183.                         "VER",   ver_cmd,
  11184.                         "EXIT",  exit_cmd, };
  11185.  
  11186.    main(int argc, char *argv[])
  11187.    {
  11188.        char input[INPSIZE];              /* keyboard input buffer */
  11189.  
  11190.        while(1)                          /* main interpreter loop */
  11191.        {
  11192.            printf("\n>> ");              /* display prompt */
  11193.            memset(input, 0, INPSIZE);    /* initialize input buffer */
  11194.            gets(input);                  /* get keyboard entry */
  11195.            strtok(input, " ;,=\t");      /* break out first token */
  11196.            strupr(input);                /* and fold to uppercase */
  11197.  
  11198.            if(! intrinsic(input))        /* if intrinsic command,
  11199.                                             run its function */
  11200.                extrinsic(input);         /* else assume EXE file */
  11201.        }
  11202.    }
  11203.  
  11204.    /*
  11205.        Try to match first token of command line with intrinsic
  11206.        command table. If a match is found, run the associated
  11207.        routine and return true; otherwise, return false.
  11208.    */
  11209.    unsigned intrinsic(char *input)
  11210.    {
  11211.        int i;                            /* scratch variable */
  11212.  
  11213.        for(i=0; i < DIM(commands); i++)  /* search command table */
  11214.        {
  11215.            if(! strcmp(commands[i].name, input))
  11216.            {
  11217.                (*commands[i].fxn)();     /* if match, run routine */
  11218.                return(1);                /* and return true */
  11219.            }
  11220.        }
  11221.        return(0);                        /* no match, return false */
  11222.    }
  11223.  
  11224.    /*
  11225.        Append .EXE to first token and attempt to execute program,
  11226.        passing address of argument strings block and null pointer
  11227.        for environment (child process inherits TINYCMD's environment).
  11228.    */
  11229.    void extrinsic(char *input)
  11230.    {
  11231.        char pbuff[INPSIZE];              /* buffer for program name */
  11232.        unsigned status;                  /* value from DosExecPgm */
  11233.        int cinfo[2];                     /* child process info */
  11234.  
  11235.        strcpy(pbuff, input);             /* copy first command token
  11236.                                             to local buffer */
  11237.        strcat(pbuff, ".EXE");            /* append .EXE to token */
  11238.  
  11239.                                          /* try to execute program */
  11240.        if(DosExecPgm(NULL,               /* object name buffer pointer */
  11241.                      0,                  /* object name buffer length */
  11242.                      0,                  /* synchronous execution mode */
  11243.                      input,              /* argument strings */
  11244.                      NULL,               /* use parent's environment */
  11245.                      cinfo,              /* receives termination info */
  11246.                      pbuff))             /* program name pointer */
  11247.  
  11248.            printf("\nBad command or filename\n");
  11249.    }
  11250.  
  11251.    /*
  11252.        These are the subroutines for the intrinsic commands.
  11253.    */
  11254.    void cls_cmd(void)                    /* CLS command */
  11255.    {
  11256.        printf("\033[2J");                /* ANSI escape sequence */
  11257.    }                                     /* to clear screen */
  11258.  
  11259.    void ver_cmd(void)                    /* VER command */
  11260.    {
  11261.        char verinfo[2];                  /* receives version info */
  11262.  
  11263.        DosGetVersion(verinfo);           /* get and display version */
  11264.        printf("\nOS/2 version %d.%02d\n", verinfo[0], verinfo[1]);
  11265.    }
  11266.  
  11267.    void exit_cmd(void)                   /* EXIT command */
  11268.    {
  11269.        exit(0);                          /* terminate TINYCMD */
  11270.    }
  11271.    ──────────────────────────────────────────────────────────────────────────
  11272.  
  11273.    Figure 12-8.  TINYCMD.C, the source code for TINYCMD.EXE, a simple command
  11274.    interpreter for OS/2.
  11275.  
  11276.    ──────────────────────────────────────────────────────────────────────────
  11277.            title   TINYCMD -- Simple Command Interpreter
  11278.            page    55,132
  11279.            .286
  11280.  
  11281.    ;
  11282.    ; TINYCMD.ASM
  11283.    ;
  11284.    ; A simple command interpreter for OS/2.
  11285.    ;
  11286.    ; Assemble with:  C> masm tinycmd.asm;
  11287.    ; Link with:  C> link tinycmd,,,os2,tinycmd
  11288.    ;
  11289.    ; Usage is:  C> tinycmd
  11290.    ;
  11291.    ; Copyright (C) 1988 Ray Duncan
  11292.    ;
  11293.  
  11294.    cr      equ     0dh                     ; ASCII carriage return
  11295.    lf      equ     0ah                     ; ASCII linefeed
  11296.    tab     equ     09h                     ; ASCII tab
  11297.    blank   equ     20h                     ; ASCII blank code
  11298.    escape  equ     01bh                    ; ASCII escape code
  11299.  
  11300.    inpsize equ     80                      ; maximum input length
  11301.            extrn   DosExecPgm:far          ; OS/2 API functions
  11302.            extrn   DosExit:far
  11303.            extrn   DosGetVersion:far
  11304.            extrn   KbdStringIn:far
  11305.            extrn   VioWrtTTY:far
  11306.  
  11307.    DGROUP  group   _DATA
  11308.  
  11309.    _DATA   segment word public 'DATA'
  11310.  
  11311.    ; Intrinsic commands table: Each entry is an ASCIIZ string
  11312.    ; followed by the offset of the corresponding procedure.
  11313.  
  11314.    commands label  byte
  11315.            db      'CLS',0                 ; clear screen command
  11316.            dw      cls_cmd
  11317.            db      'VER',0                 ; display OS/2 version
  11318.            dw      ver_cmd
  11319.            db      'EXIT',0                ; exit from TINYCMD
  11320.            dw      exit_cmd
  11321.            db      0                       ; end of table
  11322.  
  11323.    input   db      (inpsize+2) dup (0)     ; keyboard input buffer
  11324.    ibinfo  dw      inpsize,0               ; input buffer info
  11325.  
  11326.    pbuff   db      (inpsize+2) dup (0)     ; program name moved here
  11327.  
  11328.    cinfo   dw      0                       ; child termination type
  11329.            dw      0                       ; child return code
  11330.  
  11331.    prompt  db      cr,lf,'>> '             ; TINYCMD's user prompt
  11332.    pr_len  equ     $-prompt
  11333.  
  11334.    delims  db      0,blank,tab,';,='       ; delimiters for first
  11335.    de_len  equ     $-delims                ; command line token
  11336.  
  11337.    pext    db      '.EXE',0                ; program file extension
  11338.    pe_len equ      $-pext
  11339.  
  11340.    wlen    dw      0                       ; receives bytes written
  11341.    verinfo db      0,0                     ; receives OS/2 version
  11342.  
  11343.    msg1    db      cr,lf,lf                ; error message
  11344.            db      'Bad command or filename'
  11345.            db      cr,lf
  11346.    msg1_len equ    $-msg1
  11347.  
  11348.    msg2    db      escape,'[2J'            ; ANSI escape sequence
  11349.    msg2_len equ    $-msg2                  ; to clear the screen
  11350.  
  11351.    msg3    db      cr,lf,lf                ; version number message
  11352.            db      'OS/2 version '
  11353.    msg3a   db      'nn.'
  11354.    msg3b   db      'nn'
  11355.            db      cr,lf
  11356.    msg3_len equ    $-msg3
  11357.  
  11358.    _DATA   ends
  11359.  
  11360.  
  11361.    _TEXT   segment word public 'CODE'
  11362.  
  11363.            assume  cs:_TEXT,ds:DGROUP,es:DGROUP
  11364.  
  11365.    main    proc    far                     ; entry point from OS/2
  11366.  
  11367.            push    ds                      ; make DGROUP addressable
  11368.            pop     es                      ; with ES too
  11369.            cld                             ; clear direction flag
  11370.  
  11371.    main1:                                  ; main interpreter loop
  11372.            call    gcmd                    ; get command from user
  11373.  
  11374.            call    intrinsic               ; check if intrinsic function
  11375.            jnc     main1                   ; yes, it was processed
  11376.  
  11377.            call    extrinsic               ; no, run EXE file, then
  11378.            jmp     main1                   ; get another command
  11379.  
  11380.    main    endp
  11381.  
  11382.    ; Try to match first command token against COMMANDS table.
  11383.    ; If match, run the routine, return Carry = False.
  11384.    ; If no match, return Carry = True.
  11385.  
  11386.    intrinsic proc  near
  11387.  
  11388.                                            ; point to command table
  11389.            mov     si,offset DGROUP:commands
  11390.  
  11391.    intr1:                                  ; try next table entry...
  11392.            cmp     byte ptr [si],0         ; end of entire table?
  11393.            je      intr6                   ; jump if table exhausted
  11394.            mov     di,offset DGROUP:input  ; point to user's command
  11395.  
  11396.    intr2:  mov     al,[si]                 ; next character from table
  11397.  
  11398.            or      al,al                   ; end of table entry?
  11399.            jz      intr3                   ; yes, jump
  11400.  
  11401.            cmp     al,[di]                 ; compare to input character
  11402.            jnz     intr4                   ; jump, found mismatch
  11403.  
  11404.            inc     si                      ; advance string pointers
  11405.            inc     di
  11406.            jmp     intr2
  11407.  
  11408.    intr3:  cmp     byte ptr [di],0         ; user's entry same length?
  11409.            jne     intr5                   ; no, not a match
  11410.  
  11411.            call    word ptr [si+1]         ; run the command routine
  11412.  
  11413.            clc                             ; return Carry = False
  11414.            ret                             ; as success signal
  11415.  
  11416.    intr4:  lodsb                           ; look for end of this
  11417.            or      al,al                   ; command string (null byte)
  11418.            jnz     intr4                   ; not end yet, loop
  11419.  
  11420.    intr5:  add     si,2                    ; skip over routine address
  11421.            jmp     intr1                   ; try to match next command
  11422.  
  11423.    intr6:  stc                             ; command not matched, exit
  11424.            ret                             ; with Carry = True
  11425.  
  11426.    intrinsic endp
  11427.  
  11428.    ; Append .EXE to first token and attempt to execute program,
  11429.    ; passing address of argument strings block and null pointer
  11430.    ; for environment. (Child process inherits TINYCMD's environment.)
  11431.  
  11432.    extrinsic proc  near
  11433.  
  11434.            mov     si,offset DGROUP:input  ; copy first token of
  11435.            mov     di,offset DGROUP:pbuff  ; user command to pbuff
  11436.    extr1:  lodsb                           ; get next character
  11437.            or      al,al                   ; found null?
  11438.            jz      extr2                   ; yes, end of token
  11439.            stosb                           ; no, copy character
  11440.            jmp     extr1                   ; and get next character
  11441.  
  11442.    extr2:  mov     si,offset DGROUP:pext   ; append .EXE extension
  11443.            mov     cx,pe_len               ; to token, forming
  11444.            rep movsb                       ; program filename
  11445.  
  11446.                                            ; try to run program...
  11447.            push    0                       ; object name buffer
  11448.            push    0
  11449.            push    0                       ; object name buffer length
  11450.            push    0                       ; synchronous execution mode
  11451.            push    ds                      ; address of argument strings
  11452.            push    offset DGROUP:input
  11453.            push    0                       ; environment pointer
  11454.            push    0
  11455.            push    ds                      ; receives termination info
  11456.            push    offset DGROUP:cinfo
  11457.            push    ds                      ; address of program name
  11458.            push    offset DGROUP:pbuff
  11459.            call    DosExecPgm              ; transfer to OS/2
  11460.            or      ax,ax                   ; was function successful?
  11461.            jz      extr3                   ; yes, jump
  11462.  
  11463.                                            ; no, display error message...
  11464.            push    ds                      ; message address
  11465.            push    offset DGROUP:msg1
  11466.            push    msg1_len                ; message length
  11467.            push    0                       ; default video handle
  11468.            call    VioWrtTTY               ; transfer to OS/2
  11469.  
  11470.    extr3:  ret                             ; back to caller
  11471.  
  11472.    extrinsic endp
  11473.  
  11474.    ; Get input from user, convert it to argument strings block
  11475.    ; by breaking out first token with null and appending double
  11476.    ; null to entire line, and convert first token to uppercase.
  11477.  
  11478.    gcmd    proc    near                    ; prompt user, get command
  11479.                                            ; display TINYCMD prompt...
  11480.            push    ds                      ; address of prompt
  11481.            push    offset DGROUP:prompt
  11482.            push    pr_len                  ; length of prompt
  11483.            push    0                       ; default video handle
  11484.            call    VioWrtTTY
  11485.  
  11486.            mov     ibinfo+2,0              ; disable buffer editing
  11487.  
  11488.                                            ; get entry from user...
  11489.            push    ds                      ; address of input buffer
  11490.            push    offset DGROUP:input
  11491.            push    ds                      ; input buffer info
  11492.            push    offset DGROUP:ibinfo
  11493.            push    0                       ; 0 = wait for input
  11494.            push    0                       ; default keyboard handle
  11495.            call    KbdStringIn
  11496.  
  11497.            mov     cx,inpsize              ; look for carriage return
  11498.            mov     al,cr                   ; if any...
  11499.            mov     di,offset DGROUP:input
  11500.            repnz scasb
  11501.            jnz     gcmd1
  11502.            dec     di
  11503.    gcmd1:  mov     word ptr [di],0         ; and replace with 2 nulls
  11504.            mov     si,offset DGROUP:input  ; break out first token...
  11505.  
  11506.    gcmd2:  lodsb                           ; compare next character
  11507.            mov     di,offset DGROUP:delims ; to delimiter table
  11508.            mov     cx,de_len
  11509.            repnz scasb
  11510.            jnz     gcmd2                   ; jump, no match
  11511.            mov     byte ptr [si-1],0       ; replace delimiter with 0
  11512.  
  11513.            mov     si,offset DGROUP:input  ; fold token to uppercase
  11514.  
  11515.    gcmd3:  cmp     byte ptr [si],0         ; end of token?
  11516.            jz      gcmd5                   ; found end, jump
  11517.            cmp     byte ptr [si],'a'
  11518.            jb      gcmd4                   ; jump if not 'a-z'
  11519.            cmp     byte ptr [si],'z'
  11520.            ja      gcmd4                   ; jump if not 'a-z'
  11521.            sub     byte ptr [si],'a'-'A'   ; convert to uppercase
  11522.    gcmd4:  inc     si                      ; go to next character
  11523.            jmp     gcmd3
  11524.  
  11525.    gcmd5:  ret                             ; back to caller
  11526.  
  11527.    gcmd    endp
  11528.  
  11529.  
  11530.    cls_cmd proc    near                    ; intrinsic CLS command
  11531.  
  11532.                                            ; send ANSI escape sequence
  11533.                                            ; to clear the screen...
  11534.            push    ds                      ; escape sequence address
  11535.            push    offset DGROUP:msg2
  11536.            push    msg2_len                ; escape sequence length
  11537.            push    0                       ; default video handle
  11538.            call    VioWrtTTY               ; transfer to OS/2
  11539.            ret                             ; back to caller
  11540.  
  11541.    cls_cmd endp
  11542.  
  11543.  
  11544.    ver_cmd proc    near                    ; intrinsic VER command
  11545.  
  11546.                                            ; get OS/2 version number...
  11547.            push    ds                      ; receives version info
  11548.            push    offset DGROUP:verinfo
  11549.            call    DosGetVersion           ; transfer to OS/2
  11550.  
  11551.                                            ; format the version...
  11552.            mov     al,verinfo              ; convert minor version
  11553.            aam                             ; number to ASCII
  11554.            add     ax,'00'
  11555.            xchg    al,ah
  11556.            mov     word ptr msg3b,ax       ; store ASCII characters
  11557.  
  11558.            mov     al,verinfo+1            ; convert major version
  11559.            aam                             ; number to ASCII
  11560.            add     ax,'00'
  11561.            xchg    al,ah
  11562.            mov     word ptr msg3a,ax       ; store ASCII characters
  11563.  
  11564.                                            ; display version number...
  11565.            push    ds                      ; message address
  11566.            push    offset DGROUP:msg3
  11567.            push    msg3_len                ; message length
  11568.            push    0                       ; default video handle
  11569.            call    VioWrtTTY               ; transfer to OS/2
  11570.  
  11571.            ret                             ; back to caller
  11572.  
  11573.    ver_cmd endp
  11574.  
  11575.  
  11576.    exit_cmd proc   near                    ; intrinsic EXIT Command
  11577.  
  11578.                                            ; final exit to OS/2...
  11579.            push    1                       ; terminate all threads
  11580.            push    0                       ; return code = 0
  11581.            call    DosExit                 ; transfer to OS/2
  11582.  
  11583.    exit_cmd endp
  11584.  
  11585.    _TEXT   ends
  11586.  
  11587.            end     main                    ; defines entry point
  11588.    ──────────────────────────────────────────────────────────────────────────
  11589.  
  11590.    Figure 12-9.  TINYCMD.ASM, the source code for TINYCMD.EXE, a simple
  11591.    command interpreter for OS/2.
  11592.  
  11593.    ──────────────────────────────────────────────────────────────────────────
  11594.    NAME TINYCMD WINDOWCOMPAT
  11595.    PROTMODE
  11596.    STACKSIZE 4096
  11597.    ──────────────────────────────────────────────────────────────────────────
  11598.  
  11599.    Figure 12-10.  TINYCMD.DEF, the module definition file for TINYCMD.ASM.
  11600.  
  11601.  
  11602.  A Simple Multithreaded Application
  11603.  
  11604.    The listings DUMBTERM.C (Figure 12-11) and DUMBTERM.ASM (Figure 12-12,
  11605.    which begins on p. 260) contain the source code for a simple multithreaded
  11606.    application. DUMBTERM is a primitive terminal emulator that exchanges
  11607.    characters between the console (keyboard and video display) and the serial
  11608.    port. The user enters Alt-X to terminate the program.
  11609.  
  11610.    DUMBTERM demonstrates the benefits of using multiple threads when a
  11611.    process must perform I/O to two or more devices. It creates one thread to
  11612.    read the serial port and write the resulting characters to the display,
  11613.    and it creates another thread to read the keyboard and write the resulting
  11614.    characters to the serial port. The threads run independently and block
  11615.    when no input is available, thus conserving CPU cycles and eliminating the
  11616.    need for polling.
  11617.  
  11618.    For simplicity, DUMBTERM does not include any logic for selecting or
  11619.    configuring the serial port based on command line switches or menus.
  11620.    DUMBTERM always opens COM1 and configures it to 2400 baud, 8 bits, no
  11621.    parity, and 1 stop bit. To modify the settings, you must edit the #define
  11622.    or EQU statements at the beginning of the source code.
  11623.  
  11624.    To compile DUMBTERM.C into the executable file DUMBTERM.EXE, type the
  11625.    following command:
  11626.  
  11627.    [C:\] CL /F 2000 DUMBTERM.C  <Enter>
  11628.  
  11629.    The /F option creates a large stack for DUMBTERM's primary thread so that
  11630.    the stacks for the two additional I/O threads can be allocated as
  11631.    automatic arrays within the main stack. You can also allocate new segments
  11632.    for the thread stacks, but you are then more likely to run into problems
  11633.    with the stack-checking routine in the C runtime library.
  11634.  
  11635.    To assemble DUMBTERM.ASM into the file DUMBTERM.OBJ, type the following
  11636.    command:
  11637.  
  11638.    [C:\] MASM DUMBTERM.ASM;  <Enter>
  11639.  
  11640.    Then, to produce DUMBTERM.EXE, link DUMBTERM.OBJ, the import library
  11641.    OS2.LIB, and DUMBTERM.DEF (Figure 12-13 on p. 269), with the following
  11642.    command:
  11643.  
  11644.    [C:\] LINK DUMBTERM,,,OS2,DUMBTERM  <Enter>
  11645.  
  11646.    ──────────────────────────────────────────────────────────────────────────
  11647.    /*
  11648.        DUMBTERM.C
  11649.  
  11650.        A simple multithreaded OS/2 terminal program that exchanges
  11651.        characters between the console and the serial port.
  11652.  
  11653.        Communication parameters for COM1 are set at 2400 baud,
  11654.        8 data bits, no parity, and 1 stop bit.
  11655.  
  11656.        Compile with:  C> cl /F 2000 dumbterm.c
  11657.  
  11658.        Usage is:  C> dumbterm
  11659.        Press Alt-X to terminate the program.
  11660.  
  11661.        Copyright (C) 1988 Ray Duncan
  11662.  
  11663.    */
  11664.  
  11665.    #define WAIT   0                        /* parameters for KbdCharIn */
  11666.    #define NOWAIT 1
  11667.  
  11668.    #define EXITKEY  0x2d                   /* Exit key = Alt-X */
  11669.  
  11670.    #define STKSIZE 2048                    /* stack size for threads */
  11671.  
  11672.    #define COMPORT "COM1"                  /* COM port configuration */
  11673.    #define BAUD    2400                    /* 110, 150 ... 19200 */
  11674.    #define PARITY  0                       /* 0=N, 1=O, 2=E, 3=M, 4=S */
  11675.    #define DATABIT 8                       /* 5-8 allowed */
  11676.    #define STOPBIT 0                       /* 0=1, 1=1.5, 2=2 */
  11677.  
  11678.    #define API unsigned extern far pascal  /* OS/2 API prototypes */
  11679.  
  11680.    API DosClose(unsigned);
  11681.    API DosCreateThread(void (far *)(), unsigned far *, void far *);
  11682.    API DosDevIOCtl(void far *, void far *, unsigned, unsigned, unsigned);
  11683.    API DosExit(unsigned, unsigned);
  11684.    API DosOpen(char far *, unsigned far *, unsigned far *, unsigned long,
  11685.                unsigned, unsigned, unsigned, unsigned long);
  11686.    API DosRead(unsigned, void far *, int, unsigned far *);
  11687.    API DosSemClear(unsigned long far *);
  11688.    API DosSemSet(unsigned long far *);
  11689.    API DosSemWait(unsigned long far *, unsigned long);
  11690.    API DosSuspendThread(unsigned);
  11691.    API DosWrite(unsigned, void far *, int, unsigned far *);
  11692.    API KbdCharIn(void far *, unsigned, unsigned);
  11693.    API KbdGetStatus(void far *, unsigned);
  11694.    API KbdSetStatus(void far *, unsigned);
  11695.    API VioWrtTTY(char far *, int, unsigned);
  11696.  
  11697.    void far comin(void);                   /* local function prototypes */
  11698.    void far comout(void);
  11699.    void errexit(char *);
  11700.  
  11701.    struct _F41Info {                       /* DosDevIOCtl info structure */
  11702.        int BaudRate;                       /* 110, 150 ... 19200 */
  11703.        } F41Info;
  11704.    struct _F42Info {                       /* DosDevIOCtl info structure */
  11705.        char DataBits;                      /* character length 5-8 bits */
  11706.        char Parity;                        /* 0=N, 1=O, 2=E, 3=Mark, 4=Space *
  11707.        char StopBits;                      /* 0=1, 1=1.5, 2=2 stop bits */
  11708.        } F42Info;
  11709.  
  11710.    struct _KbdInfo {                       /* KbdCharIn info structure */
  11711.        char CharCode;                      /* ASCII character code */
  11712.        char ScanCode;                      /* keyboard scan code */
  11713.        char Status;                        /* miscellaneous status flags */
  11714.        char Reserved1;                     /* reserved byte */
  11715.        unsigned ShiftState;                /* keyboard shift state */
  11716.        long TimeStamp;                     /* character timestamp */
  11717.        } KbdInfo;
  11718.  
  11719.    struct _KbdStatus {                     /* KbdGetStatus info structure */
  11720.        int Length;                         /* length of structure */
  11721.        unsigned Mask;                      /* keyboard state flags */
  11722.        char TurnAround[2];                 /* logical end-of-line */
  11723.        unsigned Interim;                   /* interim character flags */
  11724.        unsigned ShiftState;                /* keyboard shift state */
  11725.        } KbdStatus;
  11726.  
  11727.    unsigned chandle;                       /* handle for COM port */
  11728.    unsigned long exitsem;                  /* Alt-X exit semaphore */
  11729.  
  11730.    main()
  11731.    {
  11732.        unsigned action;                    /* result of DosOpen */
  11733.        unsigned openflag = 0x01;           /* fail if device not found */
  11734.        unsigned openmode = 0x12;           /* read/write, deny all */
  11735.        unsigned cominID;                   /* COM input thread ID */
  11736.        unsigned comoutID;                  /* COM output thread ID */
  11737.        char cominstk[STKSIZE];             /* COM input thread stack */
  11738.        char comoutstk[STKSIZE];            /* COM output thread stack */
  11739.  
  11740.                                            /* open COM port */
  11741.        if(DosOpen(COMPORT, &chandle, &action, 0L, 0, openflag, openmode, 0L))
  11742.            errexit("\nCan't open COM device.\n");
  11743.  
  11744.        F41Info.BaudRate = BAUD;            /* configure COM port */
  11745.        F42Info.DataBits = DATABIT;         /* using DosDevIOCtl */
  11746.        F42Info.Parity = PARITY;            /* Category 1 functions */
  11747.        F42Info.StopBits = STOPBIT;
  11748.        if(DosDevIOCtl(NULL, &F41Info, 0x41, 1, chandle))
  11749.            errexit("\nCan't set baud rate.\n");
  11750.        if(DosDevIOCtl(NULL, &F42Info, 0x42, 1, chandle))
  11751.            errexit("\nCan't configure COM port.\n");
  11752.  
  11753.        KbdStatus.Length = 10;              /* force keyboard */
  11754.        KbdGetStatus(&KbdStatus, 0);        /* into binary mode */
  11755.        KbdStatus.Mask &= 0xfff0;           /* with echo off */
  11756.        KbdStatus.Mask |= 0x06;
  11757.        KbdSetStatus(&KbdStatus, 0);
  11758.  
  11759.        exitsem = 0L;                       /* initialize and */
  11760.        DosSemSet(&exitsem);                /* set exit semaphore */
  11761.  
  11762.                                            /* create COM input thread */
  11763.        if(DosCreateThread(comin, &cominID, cominstk+STKSIZE))
  11764.            errexit("\nCan't create COM input thread.\n");
  11765.  
  11766.                                            /* create COM output thread */
  11767.        if(DosCreateThread(comout, &comoutID, comoutstk+STKSIZE))
  11768.            errexit("\nCan't create COM output thread.\n");
  11769.  
  11770.        puts("\nCommunicating...");         /* sign-on message */
  11771.  
  11772.        DosSemWait(&exitsem, -1L);          /* wait for exit signal */
  11773.  
  11774.        DosSuspendThread(cominID);          /* freeze COM input and */
  11775.        DosSuspendThread(comoutID);         /* output threads */
  11776.  
  11777.        puts("\nTerminating...");           /* sign-off message */
  11778.  
  11779.        DosExit(1, 0);                      /* terminate all threads */
  11780.                                            /* return code = 0 */
  11781.    }
  11782.  
  11783.    /*
  11784.        The 'comin' thread reads characters one at a time from the
  11785.        COM port and sends them to the display.
  11786.    */
  11787.    void far comin(void)
  11788.    {
  11789.        unsigned rlen;                      /* scratch variable */
  11790.        char inchar;                        /* character input buffer */
  11791.        while(1)
  11792.        {                                   /* read character from COM */
  11793.            DosRead(chandle, &inchar, 1, &rlen);
  11794.            VioWrtTTY(&inchar, 1, 0);       /* send character to display */
  11795.        }
  11796.    }
  11797.  
  11798.    /*
  11799.        The 'comout' thread reads characters one at a time from the
  11800.        keyboard and sends them to the COM port.  If the exit key is
  11801.        detected, the main thread is signaled to clean up and exit.
  11802.    */
  11803.    void far comout(void)
  11804.    {
  11805.        unsigned wlen;                      /* scratch variable */
  11806.  
  11807.        while(1)
  11808.        {
  11809.            KbdCharIn(&KbdInfo, WAIT, 0);   /* read keyboard */
  11810.            wlen = 1;                       /* assume 1-byte character */
  11811.  
  11812.                                            /* extended character? */
  11813.            if((KbdInfo.CharCode == 0) || (KbdInfo.CharCode == 0xe0))
  11814.            {
  11815.                wlen = 2;                   /* yes, set length = 2 */
  11816.  
  11817.                if(KbdInfo.ScanCode == EXITKEY)
  11818.                {                           /* if exit key detected */
  11819.                    DosSemClear(&exitsem);  /* signal thread 1 to exit */
  11820.                    wlen = 0;               /* and discard character */
  11821.                }
  11822.            }
  11823.                                            /* write COM port */
  11824.            DosWrite(chandle, &KbdInfo.CharCode, wlen, &wlen);
  11825.        }
  11826.    }
  11827.  
  11828.    /*
  11829.        Common error exit routine; displays error message
  11830.        and terminates process.
  11831.    */
  11832.    void errexit(char *msg)
  11833.    {
  11834.        VioWrtTTY(msg, strlen(msg), 0);     /* display error message */
  11835.        DosExit(1, 1);                      /* terminate, exit code = 1 */
  11836.    }
  11837.    ──────────────────────────────────────────────────────────────────────────
  11838.  
  11839.    Figure 12-11.  DUMBTERM.C, the source code for DUMBTERM.EXE, a simple
  11840.    terminal emulator that illustrates use of multiple threads.
  11841.  
  11842.    ──────────────────────────────────────────────────────────────────────────
  11843.            title   DUMBTERM -- OS/2 Terminal Program
  11844.            page    55,132
  11845.            .286
  11846.  
  11847.    ;
  11848.    ; DUMBTERM.ASM
  11849.    ;
  11850.    ; A simple multithreaded OS/2 terminal program that exchanges
  11851.    ; characters between the console and the serial port.
  11852.    ;
  11853.    ; Communication parameters for COM1 are set at 2400 baud,
  11854.    ; 8 data bits, no parity, and 1 stop bit.
  11855.    ;
  11856.    ; Assemble with:  C> masm dumbterm.asm;
  11857.    ; Link with:  C> link dumbterm,,,os2,dumbterm
  11858.    ;
  11859.    ; Usage is:  C> dumbterm
  11860.    ;
  11861.    ; Press Alt-X to terminate the program.
  11862.    ;
  11863.    ; Copyright (C) 1988 Ray Duncan
  11864.    ;
  11865.  
  11866.    cr      equ     0dh             ; ASCII carriage return
  11867.    lf      equ     0ah             ; ASCII linefeed
  11868.  
  11869.    kwait   equ     0               ; KbdCharIn parameters
  11870.    knowait equ     1
  11871.  
  11872.    stksize equ     2048            ; stack size for threads
  11873.  
  11874.    exitkey equ     2dh             ; Alt-X key is exit signal
  11875.  
  11876.                                    ; COM port configuration
  11877.    com1    equ     1               ; nonzero if using COM1
  11878.    com2    equ     0               ; nonzero if using COM2
  11879.    com3    equ     0               ; nonzero if using COM3
  11880.    baud    equ     2400            ; baud rate for COM port
  11881.    parity  equ     0               ; 0=N, 1=O, 2=E, 3=M, 4=S
  11882.    databit equ     8               ; 5-8
  11883.    stopbit equ     0               ; 0=1, 1=1.5, 2=2
  11884.  
  11885.            extrn   DosAllocSeg:far ; OS/2 API functions
  11886.            extrn   DosClose:far
  11887.            extrn   DosCreateThread:far
  11888.            extrn   DosDevIOCtl:far
  11889.            extrn   DosExit:far
  11890.            extrn   DosOpen:far
  11891.            extrn   DosRead:far
  11892.            extrn   DosSemClear:far
  11893.            extrn   DosSemSet:far
  11894.            extrn   DosSemWait:far
  11895.            extrn   DosSuspendThread:far
  11896.            extrn   DosWrite:far
  11897.            extrn   KbdCharIn:far
  11898.            extrn   KbdGetStatus:far
  11899.            extrn   KbdSetStatus:far
  11900.            extrn   VioWrtTTY:far
  11901.  
  11902.    DGROUP  group   _DATA
  11903.  
  11904.    _DATA   segment word public 'DATA'
  11905.  
  11906.    cname   label   byte            ; COM port logical name
  11907.            if      com1
  11908.            db      'COM1',0        ; COM1 device
  11909.            endif
  11910.            if      com2
  11911.            db      'COM2',0        ; COM2 device
  11912.            endif
  11913.            if      com3
  11914.            db      'COM3',0        ; COM3 device
  11915.            endif
  11916.  
  11917.    f41info dw      baud            ; baud rate
  11918.  
  11919.    f42info db      databit         ; data bits
  11920.            db      parity          ; parity
  11921.            db      stopbit         ; stop bits
  11922.  
  11923.    kbdinfo db      0               ; character code
  11924.            db      0               ; scan code
  11925.            db      0               ; status flags
  11926.            db      0               ; reserved
  11927.            dw      0               ; shift state
  11928.            dd      0               ; timestamp
  11929.  
  11930.    kbdstat dw      10              ; length of structure
  11931.            dw      0               ; keyboard state flags
  11932.            db      0,0             ; logical end-of-line
  11933.            dw      0               ; interim character flags
  11934.            dw      0               ; shift state
  11935.    exitsem dd      0               ; exit semaphore
  11936.  
  11937.    chandle dw      0               ; receives COM device handle
  11938.    action  dw      0               ; receives DosOpen action
  11939.    sel     dw      0               ; receives selector
  11940.    rlen    dw      0               ; receives bytes read count
  11941.    wlen    dw      0               ; receives bytes written count
  11942.    cinID   dw      0               ; COM input thread ID
  11943.    coutID  dw      0               ; COM output thread ID
  11944.    inchar  db      0               ; input character from COM port
  11945.  
  11946.    msg1    db      cr,lf
  11947.            db      "Can't open COM device."
  11948.            db      cr,lf
  11949.    msg1_len equ    $-msg1
  11950.  
  11951.    msg2    db      cr,lf
  11952.            db      "Can't set baud rate."
  11953.            db      cr,lf
  11954.    msg2_len equ    $-msg2
  11955.  
  11956.    msg3    db      cr,lf
  11957.            db      "Can't configure COM port."
  11958.            db      cr,lf
  11959.    msg3_len equ    $-msg3
  11960.  
  11961.    msg4    db      cr,lf
  11962.            db      "Memory allocation failure."
  11963.            db      cr,lf
  11964.    msg4_len equ    $-msg4
  11965.  
  11966.    msg5    db      cr,lf
  11967.            db      "Can't create COM input thread."
  11968.            db      cr,lf
  11969.    msg5_len equ    $-msg5
  11970.  
  11971.    msg6    db      cr,lf
  11972.            db      "Can't create COM output thread."
  11973.            db      cr,lf
  11974.    msg6_len equ    $-msg6
  11975.  
  11976.    msg7    db      cr,lf
  11977.            db      "Communicating..."
  11978.            db      cr,lf
  11979.    msg7_len equ    $-msg7
  11980.    msg8    db      cr,lf
  11981.            db      "Terminating..."
  11982.            db      cr,lf
  11983.    msg8_len equ    $-msg8
  11984.  
  11985.    _DATA   ends
  11986.  
  11987.  
  11988.    _TEXT   segment word public 'CODE'
  11989.  
  11990.            assume  cs:_TEXT,ds:DGROUP
  11991.  
  11992.    main    proc    far             ; entry point from OS/2
  11993.  
  11994.                                    ; open COM port...
  11995.            push    ds              ; device name address
  11996.            push    offset DGROUP:cname
  11997.            push    ds              ; receives device handle
  11998.            push    offset DGROUP:chandle
  11999.            push    ds              ; receives action
  12000.            push    offset DGROUP:action
  12001.            push    0               ; initial allocation (N/A)
  12002.            push    0
  12003.            push    0               ; attribute (N/A)
  12004.            push    1               ; open flag: fail if device
  12005.                                    ;            does not exist
  12006.            push    12h             ; open mode: read/write,
  12007.                                    ;            deny-all
  12008.            push    0               ; DWORD reserved
  12009.            push    0
  12010.            call    DosOpen         ; transfer to OS/2
  12011.            or      ax,ax           ; was function successful?
  12012.            jz      main1           ; yes, jump
  12013.  
  12014.            mov     cx,msg1_len     ; no, display error message
  12015.            mov     dx,offset DGROUP:msg1
  12016.            jmp     main9           ; and exit
  12017.  
  12018.    main1:                          ; set baud rate...
  12019.            push    0               ; data buffer address
  12020.            push    0
  12021.            push    ds              ; parameter buffer address
  12022.            push    offset DGROUP:f41info
  12023.            push    41h             ; function number
  12024.            push    1               ; category number
  12025.            push    chandle         ; COM device handle
  12026.            call    DosDevIOCtl     ; transfer to OS/2
  12027.            or      ax,ax           ; was function successful?
  12028.            jz      main2           ; yes, jump
  12029.  
  12030.            mov     cx,msg2_len     ; no, display error message
  12031.            mov     dx,offset DGROUP:msg2
  12032.            jmp     main9           ; and exit
  12033.  
  12034.    main2:                          ; configure parity, stop bits,
  12035.                                    ; and character length...
  12036.            push    0               ; data buffer address
  12037.            push    0
  12038.            push    ds              ; parameter buffer address
  12039.            push    offset DGROUP:f42info
  12040.            push    42h             ; function number
  12041.            push    1               ; category number
  12042.            push    chandle         ; COM device handle
  12043.            call    DosDevIOCtl     ; transfer to OS/2
  12044.            or      ax,ax           ; was function successful?
  12045.            jz      main3           ; yes, jump
  12046.  
  12047.            mov     cx,msg3_len     ; no, display error message
  12048.            mov     dx,offset DGROUP:msg3
  12049.            jmp     main9           ; and exit
  12050.  
  12051.    main3:                          ; put keyboard in binary mode
  12052.                                    ; with echo off...
  12053.  
  12054.                                    ; get keyboard state
  12055.            push    ds              ; address of info structure
  12056.            push    offset DGROUP:kbdstat
  12057.            push    0               ; default keyboard handle
  12058.            call    KbdGetStatus    ; transfer to OS/2
  12059.  
  12060.                                    ; set binary mode, no echo
  12061.            and     word ptr kbdstat+2,0fff0h
  12062.            or      word ptr kbdstat+2,6
  12063.  
  12064.                                    ; set keyboard state
  12065.            push    ds              ; address of info structure
  12066.            push    offset DGROUP:kbdstat
  12067.            push    0               ; default keyboard handle
  12068.            call    KbdSetStatus    ; transfer to OS/2
  12069.            push    ds              ; set exit semaphore
  12070.            push    offset DGROUP:exitsem
  12071.            call    DosSemSet       ; transfer to OS/2
  12072.  
  12073.                                    ; allocate thread stack
  12074.            push    stksize         ; stack size in bytes
  12075.            push    ds              ; receives new selector
  12076.            push    offset DGROUP:sel
  12077.            push    0               ; not shareable/discardable
  12078.            call    DosAllocSeg     ; transfer to OS/2
  12079.            or      ax,ax           ; was function successful?
  12080.            jz      main4           ; yes, jump
  12081.  
  12082.            mov     cx,msg4_len     ; no, display error message
  12083.            mov     dx,offset DGROUP:msg4
  12084.            jmp     main9           ; and exit
  12085.  
  12086.    main4:                          ; create COM input thread
  12087.            push    cs              ; thread entry point
  12088.            push    offset _TEXT:comin
  12089.            push    ds              ; receives thread ID
  12090.            push    offset DGROUP:cinID
  12091.            push    sel             ; address of stack base
  12092.            push    stksize
  12093.            call    DosCreateThread ; transfer to OS/2
  12094.            or      ax,ax           ; was function successful?
  12095.            jz      main5           ; yes, jump
  12096.  
  12097.            mov     cx,msg5_len     ; no, display error message
  12098.            mov     dx,offset DGROUP:msg5
  12099.            jmp     main9           ; and exit
  12100.  
  12101.    main5:                          ; allocate thread stack
  12102.            push    stksize         ; stack size in bytes
  12103.            push    ds              ; receives new selector
  12104.            push    offset DGROUP:sel
  12105.            push    0               ; not shareable/discardable
  12106.            call    DosAllocSeg     ; transfer to OS/2
  12107.            or      ax,ax           ; was function successful?
  12108.            jz      main6           ; yes, jump
  12109.  
  12110.            mov     cx,msg4_len     ; no, display error message
  12111.            mov     dx,offset DGROUP:msg4
  12112.            jmp     main9           ; and exit
  12113.    main6:                          ; create COM output thread...
  12114.            push    cs              ; thread entry point
  12115.            push    offset _TEXT:comout
  12116.            push    ds              ; receives thread ID
  12117.            push    offset DGROUP:coutID
  12118.            push    sel             ; address of stack base
  12119.            push    stksize
  12120.            call    DosCreateThread ; transfer to OS/2
  12121.            or      ax,ax           ; was function successful?
  12122.            jz      main7           ; yes, jump
  12123.  
  12124.            mov     cx,msg6_len     ; no, display error message
  12125.            mov     dx,offset DGROUP:msg6
  12126.            jmp     main9           ; and exit
  12127.  
  12128.    main7:                          ; display "Communicating..."
  12129.            push    ds              ; message address
  12130.            push    offset DGROUP:msg7
  12131.            push    msg7_len        ; message length
  12132.            push    0               ; default video handle
  12133.            call    VioWrtTTY       ; transfer to OS/2
  12134.  
  12135.                                    ; wait for exit signal
  12136.            push    ds              ; semaphore handle
  12137.            push    offset DGROUP:exitsem
  12138.            push    -1              ; wait indefinitely
  12139.            push    -1
  12140.            call    DosSemWait      ; transfer to OS/2
  12141.  
  12142.                                    ; suspend COM input thread
  12143.            push    cinID           ; thread ID
  12144.            call    DosSuspendThread ; transfer to OS/2
  12145.  
  12146.                                    ; suspend COM output thread
  12147.            push    coutID          ; thread ID
  12148.            call    DosSuspendThread ; transfer to OS/2
  12149.  
  12150.                                    ; display "Terminating"...
  12151.            push    ds              ; message address
  12152.            push    offset DGROUP:msg8
  12153.            push    msg8_len        ; message length
  12154.            push    0               ; default video handle
  12155.            call    VioWrtTTY       ; transfer to OS/2
  12156.  
  12157.                                    ; final exit to OS/2...
  12158.            push    1               ; terminate all threads
  12159.            push    0               ; return code = 0 (success)
  12160.            call    DosExit         ; transfer to OS/2
  12161.    main9:                          ; display error message...
  12162.                                    ; DS:DX = msg, CX = length
  12163.            push    ds              ; address of message
  12164.            push    dx
  12165.            push    cx              ; length of message
  12166.            push    0               ; default video handle
  12167.            call    VioWrtTTY       ; transfer to OS/2
  12168.  
  12169.                                    ; final exit to OS/2...
  12170.            push    1               ; terminate all threads
  12171.            push    1               ; return code = 1 (error)
  12172.            call    DosExit         ; transfer to OS/2
  12173.  
  12174.    main    endp
  12175.  
  12176.  
  12177.    ; COM input thread: Reads characters one at a time
  12178.    ; from the COM port and writes them to the display.
  12179.  
  12180.    comin   proc    far
  12181.  
  12182.                                    ; read character from COM...
  12183.            push    chandle         ; COM device handle
  12184.            push    ds              ; receives COM data
  12185.            push    offset DGROUP:inchar
  12186.            push    1               ; length to read
  12187.            push    ds              ; receives read count
  12188.            push    offset DGROUP:rlen
  12189.            call    DosRead         ; transfer to OS/2
  12190.  
  12191.                                    ; send character to display...
  12192.            push    ds              ; address of character
  12193.            push    offset DGROUP:inchar
  12194.            push    1               ; length to write
  12195.            push    0               ; default video handle
  12196.            call    VioWrtTTY       ; transfer to OS/2
  12197.  
  12198.            jmp     comin           ; wait for next character
  12199.  
  12200.    comin   endp
  12201.  
  12202.  
  12203.    ; COM output thread: Reads characters from the keyboard
  12204.    ; in raw mode and sends them to the COM port.  If the
  12205.    ; exit key is detected, the main thread is signaled to
  12206.    ; clean up and terminate the process.
  12207.    comout  proc    far
  12208.  
  12209.                                    ; read keyboard character...
  12210.            push    ds              ; receives keyboard data
  12211.            push    offset DGROUP:kbdinfo
  12212.            push    kwait           ; wait if necessary
  12213.            push    0               ; default keyboard handle
  12214.            call    KbdCharIn       ; transfer to OS/2
  12215.  
  12216.            mov     cx,1            ; assume writing one byte
  12217.  
  12218.            cmp     kbdinfo,0       ; check for extended key
  12219.            jz      comout1         ; jump, extended key
  12220.            cmp     kbdinfo,0e0h
  12221.            jnz     comout2         ; jump, not extended key
  12222.  
  12223.    comout1:                        ; extended key detected
  12224.            mov     cx,2            ; must write 2 bytes
  12225.  
  12226.                                    ; check for exit key
  12227.            cmp     kbdinfo+1,exitkey
  12228.            jnz     comout2         ; not exit key, jump
  12229.  
  12230.                                    ; clear exit semaphore...
  12231.            push    ds              ; semaphore address
  12232.            push    offset DGROUP:exitsem
  12233.            call    DosSemClear     ; transfer to OS/2
  12234.            jmp     comout          ; discard exit key
  12235.  
  12236.    comout2:                        ; send character to COM port...
  12237.            push    chandle         ; COM device handle
  12238.            push    ds              ; address of character
  12239.            push    offset DGROUP:kbdinfo
  12240.            push    cx              ; length to write
  12241.            push    ds              ; receives write count
  12242.            push    offset DGROUP:wlen
  12243.            call    DosWrite        ; transfer to OS/2
  12244.  
  12245.            jmp     comout          ; wait for next key
  12246.  
  12247.    comout  endp
  12248.  
  12249.    _TEXT   ends
  12250.  
  12251.            end     main
  12252.    ──────────────────────────────────────────────────────────────────────────
  12253.  
  12254.    Figure 12-12.  DUMBTERM.ASM, the source code for DUMBTERM.EXE, a simple
  12255.    terminal application that illustrates use of multiple threads.
  12256.  
  12257.    ──────────────────────────────────────────────────────────────────────────
  12258.    NAME DUMBTERM NOTWINDOWCOMPAT
  12259.    PROTMODE
  12260.    STACKSIZE 4096
  12261.    ──────────────────────────────────────────────────────────────────────────
  12262.  
  12263.    Figure 12-13.  DUMBTERM.DEF, the module definition file for DUMBTERM.ASM.
  12264.  
  12265.  
  12266.  
  12267.  ────────────────────────────────────────────────────────────────────────────
  12268.  Chapter 13  Interprocess Communication
  12269.  
  12270.    The preceding chapter discussed the OS/2 multitasking services that
  12271.    applications use to create multiple threads within a process or to create
  12272.    child processes. However, as we have seen, the multitasking functions
  12273.    provide threads and processes with only limited control over one another.
  12274.    A thread can suspend or change the priority of another thread; it can also
  12275.    prevent other threads from interfering with its execution. A parent
  12276.    process can pass information to a child at load time, obtain the child's
  12277.    exit code and termination type, or unilaterally kill a child process.
  12278.  
  12279.    OS/2's Interprocess Communication (IPC) functions supplement the
  12280.    multitasking functions with mechanisms for the exchange of all kinds of
  12281.    information between threads and processes. The IPC functions fall into
  12282.    five categories:
  12283.  
  12284.    ■  Semaphores
  12285.  
  12286.    ■  Pipes
  12287.  
  12288.    ■  Shared memory
  12289.  
  12290.    ■  Queues
  12291.  
  12292.    ■  Signals
  12293.  
  12294.    When the OS/2 LAN Manager is running, an additional IPC mechanism called
  12295.    mailslots is also available (not discussed in this book).
  12296.  
  12297.    While reading about IPC functions, bear in mind the distinctions between
  12298.    processes and threads that were explained in Chapter 12. When a process
  12299.    opens or creates a semaphore, pipe, queue, or shared memory segment, the
  12300.    system returns a handle or selector that any thread within that process
  12301.    can use. But when a thread issues an OS/2 function call that blocks on
  12302.    (waits for) an event (such as the clearing or release of a semaphore) or
  12303.    performs a synchronous read or write to a pipe or queue, only the calling
  12304.    thread is suspended──other threads in the process continue to run as
  12305.    before.
  12306.  
  12307.  
  12308.  Semaphores
  12309.  
  12310.    Think of a semaphore as a simple object with two states: set (owned) or
  12311.    cleared (not owned). Semaphores are a high performance IPC mechanism
  12312.    because they are always resident in memory and because the OS/2 routines
  12313.    that manipulate them are extremely fast. Figure 13-1 provides a summary
  12314.    of the OS/2 semaphore-related API.
  12315.  
  12316.    Function                Description
  12317.    ──────────────────────────────────────────────────────────────────────────
  12318.    Access to System Semaphores
  12319.    DosCloseSem            Closes system semaphore
  12320.    DosCreateSem           Creates system semaphore
  12321.    DosOpenSem             Opens existing system semaphore
  12322.  
  12323.    Semaphore Functions for Mutual Exclusion
  12324.    DosSemClear            Releases ownership of semaphore
  12325.    DosSemRequest          Obtains ownership of semaphore
  12326.  
  12327.    Semaphore Functions for Signaling
  12328.    DosMuxSemWait          Waits for any of several semaphores to be clear
  12329.    DosSemClear            Unconditionally clears semaphore
  12330.    DosSemSet              Unconditionally sets semaphore
  12331.    DosSemSetWait          Unconditionally sets semaphore and waits until it
  12332.                            is clear
  12333.    DosSemWait             Waits until semaphore is clear
  12334.  
  12335.    Fast-Safe RAM Semaphore Functions
  12336.    DosFSRamSemClear       Releases fast-safe RAM semaphore
  12337.    DosFSRamSemRequest     Obtains ownership of fast-safe RAM semaphore
  12338.    ──────────────────────────────────────────────────────────────────────────
  12339.  
  12340.    Figure 13-1.  Semaphore-related API at a glance. Semaphores can be used to
  12341.    coordinate use of a nonreentrant resource or for signaling between threads
  12342.    or processes.
  12343.  
  12344.    The classic use of semaphores, which OS/2 fully supports, is to control
  12345.    access to a serially reusable resource (SRR). An SRR is a procedure, a
  12346.    device, or a data object that will be damaged or will produce
  12347.    unpredictable results if it is used by more than one thread or process at
  12348.    a time. Arranging mutual exclusion among cooperating processes is easy
  12349.    with semaphores. A semaphore is established that represents the resource,
  12350.    and a thread or process refrains from accessing the resource unless it
  12351.    "owns" the corresponding semaphore.
  12352.  
  12353.    For example, suppose that a process has opened an indexed database and
  12354.    that several threads within the process want to read or write the database
  12355.    using the same file handle. Each access to the file requires at least two
  12356.    distinct API requests──a DosChgFilePtr followed by a DosRead or DosWrite──
  12357.    so each thread must prevent its sequence of API calls from being jumbled
  12358.    with similar sequences by other threads.
  12359.  
  12360.    An easy way for a thread to protect its file operations is to make them a
  12361.    critical section of code:
  12362.  
  12363.    DosEnterCritSec
  12364.        │
  12365.        
  12366.    DosChgFilePtr
  12367.        │
  12368.        
  12369.    DosRead or DosWrite
  12370.        │
  12371.        
  12372.    DosExitCritSec
  12373.  
  12374.    But this cavalier approach shuts down all other threads in the same
  12375.    process during the file operation, whether they are trying to use the file
  12376.    or not. It specifically throws away a major benefit of multitasking: the
  12377.    ability to continue execution during an I/O operation. A far better
  12378.    approach is to set up a semaphore and then abide by the convention that
  12379.    only the thread that owns the semaphore can use the file handle:
  12380.  
  12381.    Acquire semaphore
  12382.        │
  12383.        
  12384.    DosChgFilePtr
  12385.        │
  12386.        
  12387.    DosRead or DosWrite
  12388.        │
  12389.        
  12390.    Release semaphore
  12391.  
  12392.    When you use this method, other threads that do not need simultaneous
  12393.    access to the file can execute unhindered.
  12394.  
  12395.    OS/2 semaphores can also be used for synchronization or for signaling
  12396.    between threads or processes. In this usage, one or more threads can
  12397.    suspend themselves by calling an API function to block on a semaphore.
  12398.    When the semaphore is cleared, threads that were blocking on that
  12399.    semaphore will wake up and run (subject to their priority, the type of
  12400.    wait operation, the semaphore's not being set again before a particular
  12401.    thread receives a timeslice, and so forth). When a semaphore is used for
  12402.    signaling and no resource that can be corrupted is involved, any thread
  12403.    that knows about the semaphore can set, clear, or test it at any time.
  12404.  
  12405.    Using semaphores for signaling is appropriate when only one thread is in a
  12406.    position to detect an event but other threads may want to take action
  12407.    based on this event. For example, a screen dump utility might decouple its
  12408.    keyboard monitoring from the unpredictable delays involved in a screen
  12409.    pop-up or disk write by establishing a separate thread for each of these
  12410.    tasks. When the keyboard thread detects the hot key, it can simply clear a
  12411.    semaphore to release the screen and disk I/O threads to do their work (see
  12412.    SNAP.ASM in Chapter 18). Coded in this manner, the process never
  12413.    compromises the ability of the keyboard thread to respond rapidly to
  12414.    keystrokes.
  12415.  
  12416.  OS/2 Semaphore Types
  12417.  
  12418.    To provide for the somewhat different requirements of interthread
  12419.    communication, interprocess communication, and dynlink libraries, OS/2
  12420.    supports three types of semaphores: RAM semaphores, system semaphores, and
  12421.    fast-safe RAM semaphores.
  12422.  
  12423.    RAM semaphores let you send signals or control resources among multiple
  12424.    threads in the same process or between multiple processes that share
  12425.    memory. Each RAM semaphore requires the allocation of a doubleword of
  12426.    storage, which should be initialized to zero before the semaphore is used.
  12427.    The handle for a RAM semaphore is simply its 32-bit address (selector and
  12428.    offset); consequently, the number of RAM semaphores that a process can
  12429.    manipulate is limited only by the amount of memory it can allocate.
  12430.  
  12431.    System semaphores are named objects that you can use for signaling or
  12432.    synchronization between processes; the name always takes the form:
  12433.  
  12434.      \SEM\path\name.ext
  12435.  
  12436.    where the path and the extension (.ext) are optional. OS/2 allocates the
  12437.    storage for a system semaphore outside the creating process's memory
  12438.    space, and the pool of available system semaphores is relatively small
  12439.    (128 in OS/2 version 1.0 and 256 in version 1.1).
  12440.  
  12441.    A system semaphore is created with the function DosCreateSem, whose
  12442.    parameters are the address of an ASCIIZ semaphore name, the address of a
  12443.    doubleword variable that receives a semaphore handle, and a flag that
  12444.    indicates whether ownership of the semaphore is exclusive or nonexclusive.
  12445.    (Only the process that owns an exclusive semaphore can change its state,
  12446.    although other processes can open the semaphore and test its state or
  12447.    block on it.) If the create operation is successful, the system returns a
  12448.    semaphore handle; the initial state of the semaphore is "not owned"
  12449.    (clear).
  12450.  
  12451.    To gain access to an existing system semaphore, call DosOpenSem. This
  12452.    function requires the addresses of an ASCIIZ semaphore name and a
  12453.    doubleword of storage to receive a semaphore handle. Opening a semaphore
  12454.    does not establish ownership of, test, or change the value of the
  12455.    semaphore. Figure 13-2 illustrates the procedure for creating or opening
  12456.    a system semaphore.
  12457.  
  12458.    ──────────────────────────────────────────────────────────────────────────
  12459.                                    ; error code
  12460.  
  12461.    sname   db      '\SEM\MYSEM',0  ; semaphore name
  12462.  
  12463.    shandle dd      0               ; receives semaphore handle
  12464.  
  12465.            .
  12466.            .
  12467.            .
  12468.                                    ; create semaphore...
  12469.            push    1               ; 1 = not exclusive
  12470.            push    ds              ; receives semaphore handle
  12471.            push    offset DGROUP:shandle
  12472.            push    ds              ; address of semaphore name
  12473.            push    offset DGROUP:sname
  12474.            call    DosCreateSem    ; transfer to OS/2
  12475.            or      ax,ax           ; semaphore created?
  12476.            jz      label1          ; yes, proceed
  12477.  
  12478.                                    ; no, does it exist?
  12479.            cmp     ax,ERROR_ALREADY_EXISTS
  12480.            jne     error           ; jump if other error
  12481.  
  12482.                                    ; semaphore exists, open it...
  12483.            push    ds              ; receives semaphore handle
  12484.            push    offset DGROUP:shandle
  12485.            push    ds              ; address of semaphore name
  12486.            push    offset DGROUP:sname
  12487.            call    DosOpenSem      ; transfer to OS/2
  12488.            or      ax,ax           ; did open succeed?
  12489.            jnz     error           ; jump if function failed
  12490.  
  12491.    label1:                         ; semaphore is now created
  12492.                                    ; or opened...
  12493.            .
  12494.            .
  12495.            .
  12496.    ──────────────────────────────────────────────────────────────────────────
  12497.  
  12498.    Figure 13-2.  Creating or opening a system semaphore.
  12499.  
  12500.    After you obtain a handle to a system semaphore, you can use it with the
  12501.    functions to test, set, and wait on semaphores exactly as you would use a
  12502.    RAM semaphore handle (which is an ordinary selector and offset). Knowing
  12503.    this, you might correctly predict that system semaphore handles, unlike
  12504.    file and pipe handles, cannot be inherited by child processes. A system
  12505.    semaphore handle is not, however, simply a special selector and offset
  12506.    combination; it is a "magic" value and cannot be used to inspect the
  12507.    contents of the semaphore.
  12508.  
  12509.    Fast-safe RAM semaphores are designed for use by dynlink libraries with
  12510.    multiple client processes; they have characteristics of both RAM and
  12511.    system semaphores. You can use only two special-purpose functions to
  12512.    manipulate them, and you cannot interchange them with normal RAM and
  12513.    system semaphores.
  12514.  
  12515.  Mutual Exclusion with Semaphores
  12516.  
  12517.    oA thread uses DosSemRequest to establish ownership of a semaphore and,
  12518.    by extension, ownership of the resource associated with the semaphore.
  12519.    DosSemRequest is called with the handle of a system or RAM semaphore and a
  12520.    timeout parameter. If the semaphore is currently unowned, the function
  12521.    marks it as owned and returns a success code. If the semaphore is already
  12522.    owned, the system either suspends the calling thread indefinitely until
  12523.    the semaphore becomes available, waits for a specified interval and then
  12524.    returns (if the semaphore does not clear) with an error code, or returns
  12525.    immediately with an error code──depending on the timeout parameter.
  12526.  
  12527.    The function DosSemClear is called with a semaphore handle; it releases
  12528.    the thread's ownership of that semaphore, which implicitly releases the
  12529.    associated resource. If the semaphore is currently unowned, no error is
  12530.    returned. If another thread has been waiting for the semaphore with a
  12531.    blocked DosSemRequest call, its request then succeeds and the suspended
  12532.    thread is reawakened.
  12533.  
  12534.    When multiple threads are waiting for the semaphore and the semaphore
  12535.    becomes available, the DosSemRequest of the thread with the highest
  12536.    priority succeeds. The threads with lower priorities might sleep through
  12537.    many requests and releases of the semaphore until no requests are pending
  12538.    from higher priority threads. If all of the waiting threads have the same
  12539.    priority, you cannot predict which thread will acquire the semaphore when
  12540.    it is released by the current owner.
  12541.  
  12542.    Recursive requests for a system semaphore are supported only if the
  12543.    semaphore was created with the exclusive option. OS/2 maintains a use
  12544.    count which is incremented by DosSemRequest and decremented by
  12545.    DosSemClear; the semaphore is not actually released until the use count
  12546.    becomes zero. Recursive use of RAM semaphores is not supported.
  12547.  
  12548.  Signaling with Semaphores
  12549.  
  12550.    A semaphore is set unconditionally by calling DosSemSet with the semaphore
  12551.    handle (Figures 13-3 and 13-4 on the following page). Similarly, you can
  12552.    unconditionally clear a semaphore by calling DosSemClear with the
  12553.    semaphore handle. If any threads are blocked on the semaphore, they are
  12554.    released and will run at the next opportunity, provided that the semaphore
  12555.    is not set again in the meantime. (The sole exception is explained below.)
  12556.  
  12557.    OS/2 contains several functions that allow a thread to suspend itself
  12558.    until one or more semaphores are cleared. The prototypal function is
  12559.    DosSemWait, which is called with a semaphore handle and a timeout option.
  12560.    If the semaphore is clear, the thread regains control immediately with no
  12561.    error; otherwise, the thread blocks as determined by the timeout option:
  12562.    wait for the semaphore indefinitely, return with an error if it fails to
  12563.    clear within the specified interval, or return immediately with an error
  12564.    if the semaphore is set. DosSemWait also returns an error if the semaphore
  12565.    handle is invalid.
  12566.  
  12567.    The function DosSemSetWait works like DosSemWait, except that it sets the
  12568.    semaphore if it was not already set, and then suspends the requesting
  12569.    thread until the semaphore is cleared by another thread or the indicated
  12570.    timeout expires. An important aspect of DosSemWait and DosSemSetWait is
  12571.    that they are level-triggered, not edge-triggered. A thread that is
  12572.    blocking on a semaphore with either of these functions could miss a quick
  12573.    clearing and resetting of the semaphore, depending on the thread's
  12574.    priority and scheduled position and on the activity of other threads in
  12575.    the system.
  12576.  
  12577.    ──────────────────────────────────────────────────────────────────────────
  12578.    sname   db      '\SEM\MYSEM',0  ; semaphore name
  12579.  
  12580.    shandle dd      0               ; receives semaphore handle
  12581.            .
  12582.            .
  12583.            .
  12584.                                    ; open system semaphore...
  12585.            push    ds              ; receives semaphore handle
  12586.            push    offset DGROUP:shandle
  12587.            push    ds              ; address of semaphore name
  12588.            push    offset DGROUP:sname
  12589.            call    DosOpenSem      ; transfer to OS/2
  12590.            or      ax,ax           ; did open succeed?
  12591.            jnz     error           ; jump if function failed
  12592.            .
  12593.            .
  12594.            .
  12595.                                    ; set system semaphore...
  12596.            push    word ptr shandle+2 ; handle from DosOpenSem
  12597.            push    word ptr shandle
  12598.            call    DosSemSet       ; transfer to OS/2
  12599.            or      ax,ax           ; did set succeed?
  12600.            jnz     error           ; jump if function failed
  12601.            .
  12602.            .
  12603.            .
  12604.    ──────────────────────────────────────────────────────────────────────────
  12605.  
  12606.    Figure 13-3.  Opening and setting a system semaphore.
  12607.  
  12608.    ──────────────────────────────────────────────────────────────────────────
  12609.    mysem   dd      0               ; storage for RAM semaphore
  12610.                                    ; (the address of mysem is
  12611.                                    ; the semaphore handle)
  12612.            .
  12613.            .
  12614.            .
  12615.                                    ; set RAM semaphore...
  12616.            push    ds              ; semaphore handle = address
  12617.            push    offset DGROUP:mysem
  12618.            call    DosSemSet       ; transfer to OS/2
  12619.            or      ax,ax           ; did set succeed?
  12620.            jnz     error           ; jump if function failed
  12621.            .
  12622.            .
  12623.            .
  12624.    ──────────────────────────────────────────────────────────────────────────
  12625.  
  12626.    Figure 13-4.  Setting a RAM semaphore. Note that a RAM semaphore should be
  12627.    initialized to zero before it is used for the first time.
  12628.  
  12629.    The function DosMuxSemWait is called with a list of as many as 16
  12630.    semaphores and a timeout option. The calling thread is suspended until any
  12631.    of the indicated semaphores is cleared or the function has timed out.
  12632.    Unlike DosSemWait and DosSemSetWait, DosMuxSemWait is edge-triggered: The
  12633.    waiting thread will always be awakened (at some point) if a semaphore in
  12634.    the list changes state from set to cleared──even if the semaphore gets set
  12635.    again before the waiting thread has an opportunity to run.
  12636.  
  12637.  Closing System Semaphores
  12638.  
  12639.    To end a process's access to a system semaphore, call DosCloseSem with the
  12640.    semaphore handle. Any subsequent use of the semaphore handle results in a
  12641.    GP fault. If a process terminates with open system semaphores, the system
  12642.    closes them on behalf of the process. A system semaphore ceases to exist
  12643.    when all processes that use the semaphore have issued DosCloseSem or
  12644.    terminated.
  12645.  
  12646.    An interesting situation occurs when a process terminates while it owns a
  12647.    system semaphore that is still being used by other processes. Any threads
  12648.    in other processes that are waiting for the semaphore to clear or that
  12649.    subsequently attempt to acquire the semaphore receive an error code
  12650.    indicating that the owning process may have terminated abnormally. One of
  12651.    those threads must then clean up the resource represented by the semaphore
  12652.    and then release the semaphore for further use with DosSemClear.
  12653.  
  12654.    A superior way of handling this cleanup problem takes advantage of the
  12655.    fact that the system always runs a process's ExitList procedures before it
  12656.    closes the process's remaining system semaphores. Any process that uses
  12657.    such semaphores should therefore use DosExitList to register an ExitList
  12658.    procedure that releases all semaphores and restores the associated system
  12659.    resources to a known state. This approach puts the burden of cleanup where
  12660.    it belongs so that other processes, which did not cause the error, do not
  12661.    have to figure out how to recover from it.
  12662.  
  12663.  Using Fast-Safe RAM Semaphores
  12664.  
  12665.    Fast-safe RAM semaphores were introduced in OS/2 version 1.1 to meet the
  12666.    needs of dynlink libraries with multiple clients. They combine the high
  12667.    speed of RAM semaphores with the system semaphore capabilities of
  12668.    "counting" and ownership tracking.
  12669.  
  12670.    Fast-safe RAM semaphores are manipulated with the DosFSRamSemRequest and
  12671.    DosFSRamSemClear functions. The fast-safe RAM semaphore itself is a
  12672.    14-byte structure in the memory space belonging to the process or dynlink
  12673.    library. The system supports nested calls to DosFSRamSemRequest by
  12674.    requiring that an equivalent number of calls to DosFSRamSemClear be issued
  12675.    by the semaphore's owner before it clears the semaphore and releases any
  12676.    blocked threads.
  12677.  
  12678.    What's special about fast-safe RAM semaphores? Just this: If an ExitList
  12679.    routine (registered with DosExitList) calls DosFSRamSemRequest for a
  12680.    fast-safe RAM semaphore that is owned by any thread in the same process,
  12681.    the DosFSRamSemRequest succeeds and the owner thread ID and reference
  12682.    count fields of the semaphore are forced to 1. The ExitList routine can
  12683.    then take any necessary measures to clean up the resource symbolized by
  12684.    the semaphore and call DosFSRamSemClear to release the semaphore.
  12685.  
  12686.    Fast-safe RAM semaphores are therefore ideal for representing a resource
  12687.    controlled by a dynlink library and accessed by multiple client processes
  12688.    of that library. When the dynlink library is called for the first time by
  12689.    a particular process, it can register an ExitList routine on behalf of
  12690.    that process that will clean up the resource and release the fast-safe RAM
  12691.    semaphore for the resource if the process terminates abnormally. Using
  12692.    fast-safe RAM semaphores, the dynlink library gets the advantages of a
  12693.    system semaphore──"counting" and cross-process usability──but avoids the
  12694.    speed penalty for a system semaphore.
  12695.  
  12696.  
  12697.  Anonymous Pipes
  12698.  
  12699.    Anonymous pipes are so called because (unlike system semaphores, named
  12700.    pipes, and queues) they do not have names and are accessed only by
  12701.    handles. They are an IPC mechanism midway in power between semaphores and
  12702.    queues. Like semaphores, pipes provide relatively high performance because
  12703.    the information they convey is always resident in memory; like queues,
  12704.    pipes can be used to pass a chunk of data of any size (up to 64 KB)
  12705.    between processes. Physically, an anonymous pipe is simply an area of
  12706.    memory owned by the operating system that is used as a ring buffer, with
  12707.    "in" and "out" pointers that are also maintained by the system. From a
  12708.    process's point of view, a pipe is manipulated somewhat like a file, but
  12709.    it acts more like a FIFO queue and is much faster.
  12710.  
  12711.    To create an anonymous pipe, call the function DosMakePipe, supplying a
  12712.    maximum pipe size (as large as 64 KB less 32 bytes for a control header)
  12713.    and the addresses of two variables that receive read and write handles for
  12714.    the pipe. The size parameter is advisory──the actual size of the pipe
  12715.    buffer can be smaller depending on available memory; in any event, the
  12716.    size affects only the pipe's performance, not its behavior. DosMakePipe
  12717.    fails if memory is insufficient to create the pipe or if the process has
  12718.    exhausted its supply of handles.
  12719.  
  12720.    The two handles returned by DosMakePipe are assigned from the same
  12721.    sequence as those returned by DosOpen for files and character devices. Any
  12722.    child processes started after the pipe is created inherit the handles and
  12723.    have access to the pipe. A common application of pipes, illustrated below,
  12724.    is to redirect a child process's standard input and standard output
  12725.    handles to pipe handles; thereafter, the child process unknowingly
  12726.    communicates with the parent process for its input and output instead of
  12727.    with the keyboard and screen. A simplified example of this technique is
  12728.    shown in Figure 13-5 on the following page.
  12729.  
  12730.    Create two pipes with DosMakePipe
  12731.         │
  12732.         
  12733.    Redirect standard device handles to pipe handles
  12734.         │
  12735.         
  12736.    DosExecPgm child process
  12737.         │
  12738.         
  12739.    Communicate with child process via pipes
  12740.         │
  12741.         
  12742.    DosCwait to synchronize with child's termination
  12743.         │
  12744.         
  12745.    Restore standard device handles to previous meanings
  12746.         │
  12747.         
  12748.    DosClose pipe handles to destroy pipes
  12749.  
  12750.    Processes that are not direct descendants of an anonymous pipe's creator
  12751.    cannot inherit the handles for the pipe and thus have no way to access it.
  12752.    The two major restrictions on the use of anonymous pipes are thus their
  12753.    limitation to use for communication between closely related processes, and
  12754.    the 64 KB limit on the amount of data that a pipe can contain "in
  12755.    transit."
  12756.  
  12757.    To read from or write to a pipe synchronously, call DosRead or DosWrite
  12758.    with the appropriate pipe handle, buffer address, and record length. You
  12759.    can read and write a pipe asynchronously with DosReadAsync and
  12760.    DosWriteAsync, which return control to the requesting thread immediately
  12761.    and clear a semaphore when the operation is complete. Because anonymous
  12762.    pipes must be used within a closely related group of processes, they have
  12763.    no associated permission mechanisms.
  12764.  
  12765.    Don't be misled by the superficial similarities between pipe handles and
  12766.    file handles──they behave in subtly different ways. If a synchronous write
  12767.    is directed to a file and the disk is full, DosWrite does not return an
  12768.    error──it simply returns a count of bytes transferred that is smaller
  12769.    than the length requested. But if a synchronous write is performed to a
  12770.    pipe handle and the pipe is full, the requesting thread is blocked until
  12771.    another thread removes enough data from the pipe so that the write can be
  12772.    completed.
  12773.  
  12774.    Similarly, if a synchronous read is performed with a file handle and the
  12775.    file pointer is at or near the end of file, DosRead returns anyway,
  12776.    indicating that a partial record or no bytes were transferred. But if
  12777.    DosRead is called with a pipe handle and the pipe is empty, the thread is
  12778.    suspended until some other thread writes enough data into the pipe to
  12779.    satisfy the request.
  12780.  
  12781.    This un-filelike behavior can change when pipe handles are closed by
  12782.    participating processes. If all read handles to a pipe are closed, a
  12783.    DosWrite to the pipe returns an error. If all write handles to a pipe are
  12784.    closed, a DosRead of the pipe returns an error.
  12785.  
  12786.    ──────────────────────────────────────────────────────────────────────────
  12787.    stdin   equ     0               ; standard input handle
  12788.  
  12789.    preadh  dw      ?               ; handle to read pipe
  12790.    pwriteh dw      ?               ; handle to write pipe
  12791.    stdinh  dw      stdin           ; stdin handle to redirect
  12792.    wlen    dw      ?               ; receives bytes written
  12793.  
  12794.    msg     db      'Hello kid!'    ; message for child process
  12795.    msg_len equ     $-msg
  12796.            .
  12797.            .
  12798.            .
  12799.                                    ; first create pipe...
  12800.            push    ds              ; receives read handle
  12801.            push    offset DGROUP:preadh
  12802.            push    ds              ; receives write handle
  12803.            push    offset DGROUP:pwriteh
  12804.            push    0               ; max pipe size = default
  12805.            call    DosMakePipe     ; transfer to OS/2
  12806.            or      ax,ax           ; was pipe created?
  12807.            jnz     error           ; jump if function failed
  12808.  
  12809.                                    ; redirect standard input
  12810.                                    ; to read from pipe...
  12811.            push    preadh          ; pipe read handle
  12812.            push    ds              ; standard input handle
  12813.            push    offset DGROUP:stdinh
  12814.            call    DosDupHandle    ; transfer to OS/2
  12815.            or      ax,ax           ; redirection successful?
  12816.            jnz     error           ; jump if function failed
  12817.  
  12818.            .                       ; start child process
  12819.            .                       ; with DosExecPgm here...
  12820.            .                       ; (code not shown)
  12821.  
  12822.                                    ; send message to child
  12823.                                    ; through pipe...
  12824.            push    pwriteh         ; pipe write handle
  12825.            push    ds              ; address of message
  12826.            push    offset DGROUP:msg
  12827.            push    msg_len         ; length of message
  12828.            push    ds              ; receives bytes written
  12829.            push    offset DGROUP:wlen
  12830.            call    DosWrite        ; transfer to OS/2
  12831.            or      ax,ax           ; did write succeed?
  12832.            jnz     error           ; jump if function failed
  12833.            .
  12834.            .
  12835.            .
  12836.    ──────────────────────────────────────────────────────────────────────────
  12837.  
  12838.    Figure 13-5.  Simplified example of IPC using anonymous pipes. A pipe is
  12839.    created, the standard input handle is redirected to the pipe's read
  12840.    handle, and a child process is started. A message written into the pipe by
  12841.    the parent is received by the child when it issues a DosRead to its
  12842.    inherited standard input handle.
  12843.  
  12844.  
  12845.  Named Pipes
  12846.  
  12847.    Named pipes were originally designed for use in the OS/2 LAN Manager, but
  12848.    as their general usefulness became apparent, they were subsumed into the
  12849.    OS/2 kernel with version 1.1. Named pipes can be used for communication
  12850.    between unrelated processes running on the same system or between
  12851.    processes running on different machines on a local area network; they
  12852.    always have names of the following form:
  12853.  
  12854.      \PIPE\path\name.ext
  12855.  
  12856.    where the path and the extension (.ext) are optional. Figure 13-6
  12857.    summarizes the OS/2 API for both anonymous and named pipes.
  12858.  
  12859.    Function                  Description
  12860.    ──────────────────────────────────────────────────────────────────────────
  12861.    Anonymous Pipes
  12862.    DosMakePipe               Creates anonymous pipe, returns read and write
  12863.                              handles
  12864.  
  12865.    Named Pipes
  12866.    DosCallNmPipe            Opens, writes, reads, then closes named pipe (C)
  12867.    DosConnectNmPipe         Waits for client to open pipe (S)
  12868.    DosDisConnectNmPipe      Unilaterally closes named pipe (S)
  12869.    DosMakeNmPipe            Creates named pipe and returns handle (S)
  12870.    DosPeekNmPipe            Looks ahead for data in named pipe (B)
  12871.    DosQNmPHandState         Returns state information for named pipe (B)
  12872.    DosQNmPipeInfo           Returns information about named pipe (B)
  12873.    DosQNmPipeSemState       Returns information for pipe associated with
  12874.                              semaphore (B)
  12875.    DosSetNmPHandState       Sets state information for named pipe (B)
  12876.    DosSetNmPipeSem          Associates a semaphore with a named pipe (B)
  12877.    DosTransactNmPipe        Writes, then reads, named pipe (B)
  12878.    DosWaitNmPipe            Conditionally waits to open named pipe (C)
  12879.    ──────────────────────────────────────────────────────────────────────────
  12880.  
  12881.    Figure 13-6.  Pipe-related IPC functions at a glance. S = usable by named
  12882.    pipe servers (creators) only; C = usable by named pipe clients only; B =
  12883.    usable both by named pipe servers and by clients.
  12884.  
  12885.    A typical application will use only a few of the functions that the API
  12886.    provides for manipulating named pipes. The pipe's creator, or server,
  12887.    calls DosMakeNmPipe to create a named pipe. The parameters for this
  12888.    function include various pipe characteristics, the maximum number of
  12889.    instances of the pipe that can exist, and advisory sizes for the pipe's
  12890.    input and output buffers.
  12891.  
  12892.    The server process can use DosConnectNmPipe to wait until another
  12893.    process has opened a pipe by name; DosRead, DosReadAsync, DosWrite,
  12894.    or DosWriteAsync to obtain data from or put data into the pipe; and
  12895.    DosPeekNmPipe to inspect data in the pipe without removing it. A server
  12896.    can also unilaterally break a pipe connection with another process with
  12897.    DosDisConnectNmPipe.
  12898.  
  12899.    A client process, that is, a process which did not create the pipe but
  12900.    wants to use it, can obtain a handle for the pipe with DosOpen and then
  12901.    access the pipe with DosRead, DosReadAsync, DosWrite, and DosWriteAsync. A
  12902.    client process can also take advantage of the special functions
  12903.    DosCallNmPipe or DosTransactNmPipe to exchange data with the pipe's
  12904.    creator in one operation. A client releases a handle for a named pipe
  12905.    explicitly with DosClose or implicitly at process termination.
  12906.  
  12907.    Both server and client processes can use the functions DosDupHandle,
  12908.    DosQHandType, DosQFHandState, DosSetFHandState, and DosBufReset with a
  12909.    handle for a named pipe as though it were a file handle. Among other
  12910.    things, this similarity allows a child process that inherits handles for a
  12911.    named pipe to determine its nature.
  12912.  
  12913.    When all the client handles for a named pipe have been closed, the pipe
  12914.    cannot be reopened until the server issues DosDisConnectNmPipe followed by
  12915.    DosConnectNmPipe. When all client and server handles for a named pipe have
  12916.    been closed, the pipe is destroyed. Figure 13-7 depicts the various
  12917.    possible states for a named pipe.
  12918.  
  12919.                                    │ Server
  12920.                                    │ DosMakeNmPipe
  12921.                             ┌─────────────┐
  12922.                             │     Pipe     │ Server DosClose
  12923.    ┌───────────────────────│ disconnected ├─────────────────┐
  12924.    │                        └──────┬───────┘                 │
  12925.    │                               │ Server                  │
  12926.    │                               │ DosConnectNmPipe        │
  12927.    │                        ┌─────────────┐                 │
  12928.    │                        │     Pipe     │ Server DosClose │
  12929.    │                        │  listening   ├────────────────│
  12930.    │                        └──────┬───────┘                 │
  12931.    │                               │ Client                  │
  12932.    │                               │ DosOpen                 │
  12933.    │  Server                ┌─────────────┐                 │
  12934.    │  DosDisConnectNmPipe   │     Pipe     │                 │
  12935.    │───────────────────────┤  connected   │                 │
  12936.    │                        └──────┬───────┘                 │
  12937.    │                               │ Client or Server        │
  12938.    │                               │ DosClose                │
  12939.    │                                                        │
  12940.    │                              /\                         │
  12941.    │                            /    \                       │
  12942.    │                          /        \                     │
  12943.    │                        /    All     \                   │
  12944.    │                      /    handles     \  Yes     ┌─────────────┐
  12945.    │                 ┌───\    closed?     /─────────│     Pipe     │
  12946.    │                 │      \            /            │  destroyed   │
  12947.    │          Client │        \        /              └──────────────┘
  12948.    │              or │          \    /
  12949.    │          Server │            \/
  12950.    │        DosClose │             │
  12951.    │                 │             │ No
  12952.    │                 │      ┌─────────────┐
  12953.    │                 └──────┤     Pipe     │
  12954.    │                        │    closing   │
  12955.    │                        └──────┬───────┘
  12956.    │ Server                        │
  12957.    │ DosDisConnectNmPipe           │
  12958.    └───────────────────────────────┘
  12959.  
  12960.    Figure 13-7.  State transitions for a named pipe.
  12961.  
  12962.    An important feature of named pipes──as compared to anonymous pipes──is
  12963.    the fact that you can create them as either byte stream pipes or message
  12964.    pipes. A byte stream pipe works like an anonymous pipe and is subject to
  12965.    the same special considerations for DosRead and DosWrite. A message pipe
  12966.    behaves somewhat like a FIFO queue; the length of each message written
  12967.    into the pipe is encoded in the pipe, and DosRead returns at most one
  12968.    message at a time regardless of the number of bytes requested. If you call
  12969.    DosRead with a buffer that is too small for the message, it returns a
  12970.    partial message along with an error code; you can recover the remainder of
  12971.    the message with additional calls to DosRead.
  12972.  
  12973.  
  12974.  Shared Memory
  12975.  
  12976.    Shared memory segments are potentially the fastest IPC mechanism. If two
  12977.    or more processes have a selector to the same segment, they can pass data
  12978.    back and forth at speeds limited only by the CPU's ability to copy bytes
  12979.    from one place to another──the mechanism requires no additional calls to
  12980.    the operating system. The threads and processes sharing the segment are
  12981.    responsible for using semaphores or other interlocks to synchronize any
  12982.    changes in its content.
  12983.  
  12984.    OS/2 supports two distinct methods by which processes can share memory:
  12985.    creation of named segments, and giving and getting of selectors. Each
  12986.    method offers specific advantages for data protection and speed of access.
  12987.  
  12988.    A named shareable segment is created by calling DosAllocShrSeg with the
  12989.    segment size, the address of a variable to receive a selector, and the
  12990.    address of an ASCIIZ segment name in the following form:
  12991.  
  12992.      \SHAREMEM\path\name.ext
  12993.  
  12994.    where the path and the extension (.ext) are optional (example in Figure
  12995.    13-8). The segment is always movable and swappable. DosAllocShrSeg fails
  12996.    if the system's virtual memory (physical memory plus swap space) is
  12997.    exhausted, if all shareable selectors are in use, or if a segment by the
  12998.    same name already exists.
  12999.  
  13000.    After you create a segment with DosAllocShrSeg, any process that knows the
  13001.    segment's name can obtain a valid selector for it with DosGetShrSeg. The
  13002.    selector for a particular named segment is identical across all processes,
  13003.    although a descriptor is not actually built in a process's LDT until the
  13004.    process invokes DosGetShrSeg.
  13005.  
  13006.    ──────────────────────────────────────────────────────────────────────────
  13007.                                    ; name of shared segment
  13008.    segname db      '\SHAREMEM\MYSEG',0
  13009.  
  13010.    sel     dw      ?               ; receives selector
  13011.  
  13012.            .
  13013.            .
  13014.            .
  13015.                                    ; create named shareable
  13016.                                    ; segment...
  13017.            push    32768           ; segment size
  13018.            push    ds              ; address of name
  13019.            push    offset DGROUP:segname
  13020.            push    ds              ; receives selector
  13021.            push    offset DGROUP:sel
  13022.            call    DosAllocShrSeg  ; transfer to OS/2
  13023.            or      ax,ax           ; segment created?
  13024.            jnz     error           ; jump if function failed
  13025.            .
  13026.            .
  13027.            .
  13028.    ──────────────────────────────────────────────────────────────────────────
  13029.  
  13030.    Figure 13-8.  Creating a 32 KB named shared memory segment.
  13031.  
  13032.    Sharing a memory segment by getting and giving selectors is somewhat more
  13033.    complicated because the selectors must be passed between the processes by
  13034.    another means of IPC (such as a pipe, a queue, or a named segment). To
  13035.    allocate a givable or gettable shared segment, call DosAllocSeg or
  13036.    DosAllocHuge in the usual manner, with the proper bits in the function's
  13037.    shareable/discardable parameter set to indicate whether new selectors and
  13038.    descriptors will be needed later and how they will be obtained.
  13039.  
  13040.    When a process gives away a selector to another process, it calls
  13041.    DosGiveSeg with its own selector for the segment and the PID of the
  13042.    receiving process. DosGiveSeg builds a descriptor in the receiving
  13043.    process's LDT that maps to the same physical memory. It returns the
  13044.    corresponding selector to the calling process, which must then communicate
  13045.    the new selector to the receiving process by some IPC method. Segment
  13046.    giving is best suited to IPC between closely related processes because the
  13047.    receiving process's PID is usually known only to its parent. Segment
  13048.    giving is demonstrated as part of the queue write example in Figure
  13049.    13-10 on p. 290.
  13050.  
  13051.    When a gettable segment is allocated, on the other hand, the memory
  13052.    manager reserves the corresponding selector position in the LDT of every
  13053.    process in the system. Initially, however, a descriptor is built only in
  13054.    the allocating process's LDT. Any other process can, once it knows the
  13055.    selector value, make that selector valid for its own access by calling
  13056.    DosGetSeg to build a corresponding LDT descriptor. Segment getting allows
  13057.    far pointers to be passed around freely, without the need for one process
  13058.    to know another's PID, but it is a correspondingly less secure technique
  13059.    than the use of givable selectors.
  13060.  
  13061.    A process can give up its right of access to a named, gettable, or givable
  13062.    shared memory segment by calling DosFreeSeg with the segment's selector.
  13063.    Subsequent use of that selector by the process results in a GP fault. OS/2
  13064.    maintains a reference count for each shared segment. When all processes
  13065.    using a segment have released their selectors for the segment or have
  13066.    terminated, OS/2 destroys the segment and returns its memory to the global
  13067.    pool.
  13068.  
  13069.  
  13070.  Queues
  13071.  
  13072.    Queues are the most powerful IPC mechanisms in OS/2 and the most complex
  13073.    to use. Queues are slower than pipes (and much slower than communication
  13074.    by semaphores or shared memory segments), but they are also far more
  13075.    flexible, as suggested by the following list of characteristics:
  13076.  
  13077.    ■  Queues are named objects which any process can open and write.
  13078.  
  13079.    ■  The amount of data in a queue is limited only by available virtual
  13080.       memory.
  13081.  
  13082.    ■  Each record in a queue is a separately allocated block of memory
  13083.       storage and can be as large as 64 KB.
  13084.  
  13085.    ■  The records in a queue can be ordered by first-in-first-out (FIFO),
  13086.       last-in-first-out (LIFO), or priority.
  13087.  
  13088.    ■  The queue owner can examine records in the queue and remove them
  13089.       selectively, in any order.
  13090.  
  13091.    ■  Data in the queue is not copied from place to place by the operating
  13092.       system; instead, pointers are passed to shared memory segments.
  13093.  
  13094.    In essence, an OS/2 queue is an ordered list of shared memory segments;
  13095.    the operating system maintains and searches the list on behalf of the
  13096.    communicating processes. Figure 13-9 summarizes the queue-related
  13097.    functions in the OS/2 API.
  13098.  
  13099.    To create a queue, call DosCreateQueue with an ASCIIZ queue name, a
  13100.    parameter that specifies ordering method for the queue (FIFO, LIFO, or
  13101.    priority), and the address of a variable to receive a queue handle. The
  13102.    name of a queue always takes the following form:
  13103.  
  13104.      \QUEUES\path\name.ext
  13105.  
  13106.    where the path and the extension (.ext) are optional. An error is returned
  13107.    if a queue with the same name already exists, if the name or queue
  13108.    ordering is invalid, or if there is not enough free memory to establish
  13109.    the queue's supporting data structure. The creator of a queue is called
  13110.    the queue owner. After a queue is created, any process can open it by
  13111.    calling DosOpenQueue with an ASCIIZ queue name and pointers to two
  13112.    variables that receive a queue handle and the PID of the queue owner.
  13113.  
  13114.    Function             Description
  13115.    ──────────────────────────────────────────────────────────────────────────
  13116.    Queue Access
  13117.    DosCloseQueue        Closes queue, destroys queue if owner (A)
  13118.    DosCreateQueue      Creates a queue and returns handle (O)
  13119.    DosOpenQueue        Opens existing queue and returns handle (A)
  13120.  
  13121.    Queue Reads and Writes
  13122.    DosPeekQueue        Nondestructively reads queue message (O)
  13123.    DosReadQueue        Reads message from queue (O)
  13124.    DosWriteQueue       Writes message into queue (A)
  13125.  
  13126.    Queue Information
  13127.    DosPurgeQueue       Discards all messages in queue (O)
  13128.    DosQueryQueue       Returns number of messages in queue (A)
  13129.    ──────────────────────────────────────────────────────────────────────────
  13130.  
  13131.    Figure 13-9.  OS/2 queue management API at a glance. O = usable only by
  13132.    queue owner; A = usable by any process.
  13133.  
  13134.    Any thread in any process that has a queue handle can write to the queue,
  13135.    but adding records to a queue is much more involved than writing a record
  13136.    to a pipe. The procedure can be summarized as follows:
  13137.  
  13138.    DosOpenQueue
  13139.        │
  13140.        
  13141.    DosAllocSeg with givable flag ──────────────────────┐
  13142.        │                                                │
  13143.                                                        │
  13144.    Copy message to segment                              │
  13145.        │                                                │
  13146.                                                        │
  13147.    DosGiveSeg to create selector for queue reader       │
  13148.        │                                                │
  13149.                                                        │
  13150.    DosWriteQueue                                        │
  13151.        │                                                │
  13152.                                                        │
  13153.    DosFreeSeg of original selector from DosAllocSeg─────┘
  13154.        │
  13155.        
  13156.    DosCloseQueue
  13157.  
  13158.    In this sequence, the writer allocates a memory segment of appropriate
  13159.    size with DosAllocSeg, specifying that the segment is givable, and copies
  13160.    the data for the queue record into it. Then the writer uses DosGiveSeg,
  13161.    together with the PID returned by DosOpenQueue, to obtain a giveaway
  13162.    selector that can be passed to the queue owner.
  13163.  
  13164.    Next, the writer calls DosWriteQueue with a queue handle, a priority, the
  13165.    selector (obtained from DosGiveSeg) and offset for the queue message, and
  13166.    the message length. DosWriteQueue fails if the queue handle is invalid or
  13167.    if system memory is insufficient to expand the supporting structure of the
  13168.    queue; obviously, the whole sequence can also fail at an earlier point (in
  13169.    either DosAllocSeg or DosGiveSeg) if the system runs out of virtual memory
  13170.    or selectors. Last, if the writer is using segments as individual queue
  13171.    messages (the usual case), it should release its original selector for the
  13172.    shareable segment with DosFreeSeg. Figure 13-10 demonstrates the writing
  13173.    of a message to a queue.
  13174.  
  13175.    ──────────────────────────────────────────────────────────────────────────
  13176.    qhandle dw      ?               ; receives queue handle
  13177.  
  13178.    qowner  dw      ?               ; receives queue owner PID
  13179.  
  13180.    qname   db      '\QUEUES\MYQ',0 ; queue name
  13181.  
  13182.    qselw   dw      ?               ; queue writer's selector
  13183.  
  13184.    qselr   dw      ?               ; queue reader's selector
  13185.  
  13186.    qmsg    db      'This is my queue message'
  13187.    qmsg_len equ    $-qmsg
  13188.  
  13189.            .
  13190.            .
  13191.            .
  13192.                                    ; first open the queue...
  13193.            push    ds              ; receives owner's PID
  13194.            push    offset DGROUP:qowner
  13195.            push    ds              ; receives queue handle
  13196.            push    offset DGROUP:qhandle
  13197.            push    ds              ; address of queue name
  13198.            push    offset DGROUP:qname
  13199.            call    DosOpenQueue    ; transfer to OS/2
  13200.            or      ax,ax           ; did open succeed?
  13201.            jnz     error           ; jump if function failed
  13202.  
  13203.                                    ; allocate givable segment
  13204.            push    qmsg_len        ; length of segment
  13205.            push    ds              ; receives segment selector
  13206.            push    offset DGROUP:qselw
  13207.            push    1               ; 1 = segment givable
  13208.            call    DosAllocSeg     ; transfer to OS/2
  13209.            or      ax,ax           ; did allocation succeed?
  13210.            jnz     error           ; jump if function failed
  13211.  
  13212.            mov     es,qselw        ; copy queue message to
  13213.            xor     di,di           ; giveaway segment
  13214.            mov     si,offset DGROUP:qmsg
  13215.            mov     cx,qmsg_len
  13216.            cld
  13217.            rep movsb
  13218.  
  13219.            push    ds              ; restore ES = DGROUP
  13220.            pop     es
  13221.  
  13222.                                    ; get giveaway selector...
  13223.            push    qselw           ; original selector
  13224.            push    qowner          ; PID of queue reader
  13225.            push    ds              ; receives new selector
  13226.            push    offset DGROUP:qselr
  13227.            call    DosGiveSeg      ; transfer to OS/2
  13228.            or      ax,ax           ; new selector obtained?
  13229.            jnz     error           ; no, can't send message
  13230.  
  13231.                                    ; write message to queue...
  13232.            push    qhandle         ; handle from DosOpenQueue
  13233.            push    0               ; private data
  13234.            push    qmsg_len        ; queue message length
  13235.            push    qselr           ; queue message address
  13236.            push    0               ; (using givable selector)
  13237.            push    0               ; queue element priority
  13238.            call    DosWriteQueue   ; transfer to OS/2
  13239.            or      ax,ax           ; did queue write succeed?
  13240.            jnz     error           ; jump if function failed
  13241.                                    ; release original selector
  13242.                                    ; for shared segment...
  13243.            push    qselw           ; original selector
  13244.            call    DosFreeSeg      ; transfer to OS/2
  13245.            or      ax,ax           ; did release succeed?
  13246.            jnz     error           ; jump if function failed
  13247.            .
  13248.            .
  13249.            .
  13250.                                    ; now close the queue...
  13251.            push    qhandle         ; handle from DosOpenQueue
  13252.            call    DosCloseQueue   ; transfer to OS/2
  13253.            or      ax,ax           ; did close succeed?
  13254.            jnz     error           ; jump if function failed
  13255.            .
  13256.            .
  13257.            .
  13258.    ──────────────────────────────────────────────────────────────────────────
  13259.  
  13260.    Figure 13-10.  Opening an existing queue, writing a message into it, and
  13261.    then closing it.
  13262.  
  13263.    Only the queue owner can inspect or remove messages in a queue. The
  13264.    procedure can be summarized as follows:
  13265.  
  13266.    DosCreateQueue
  13267.        │
  13268.        
  13269.    DosReadQueue───┐
  13270.        │           │
  13271.                   │
  13272.    DosFreeSeg──────┘
  13273.        │
  13274.        
  13275.    DosCloseQueue
  13276.  
  13277.    The principal queue-reading function, DosReadQueue, is called with a queue
  13278.    handle and several other selection parameters; it returns a pointer and
  13279.    length for a queue element. The owner can choose a message from the queue
  13280.    based on its position or priority or simply take the next message in line.
  13281.    Reading a queue can be a synchronous operation (with the calling thread
  13282.    suspended until a record is available) or an asynchronous operation (with
  13283.    the calling thread regaining control immediately and a semaphore cleared
  13284.    when a message becomes available). If messages are being passed one per
  13285.    segment, the queue reader should release the selector as soon as it has
  13286.    processed the message so that the amount of virtual memory tied up in the
  13287.    queue will not grow out of control. Figure 13-11 offers an example of
  13288.    reading a message from a queue.
  13289.  
  13290.    ──────────────────────────────────────────────────────────────────────────
  13291.    qhandle dw      ?               ; receives queue handle
  13292.  
  13293.    qname   db      '\QUEUES\MYQ',0 ; queue name
  13294.  
  13295.    qident  dw      0,0             ; writer's PID, event code
  13296.  
  13297.    qmsglen dw      ?               ; length of queue message
  13298.  
  13299.    qmsgptr dd      0               ; address of queue message
  13300.  
  13301.    qmsgpri dw      ?               ; priority of queue message
  13302.  
  13303.            .
  13304.            .
  13305.            .
  13306.                                    ; first create queue...
  13307.            push    ds              ; receives queue handle
  13308.            push    offset DGROUP:qhandle
  13309.            push    0               ; 0 = FIFO queue ordering
  13310.            push    ds              ; address of queue name
  13311.            push    offset DGROUP:qname
  13312.            call    DosCreateQueue  ; transfer to OS/2
  13313.            or      ax,ax           ; was create successful?
  13314.            jnz     error           ; jump if function failed
  13315.  
  13316.                                    ; read message from queue...
  13317.            push    qhandle         ; queue handle
  13318.            push    ds              ; receives PID, event code
  13319.            push    offset DGROUP:qident
  13320.            push    ds              ; receives message length
  13321.            push    offset DGROUP:qmsglen
  13322.            push    ds              ; receives message pointer
  13323.            push    offset DGROUP:qmsgptr
  13324.            push    0               ; 0 = read first element
  13325.            push    0               ; 0 = synchronous read
  13326.            push    ds              ; receives message priority
  13327.            push    offset DGROUP:qmsgpri
  13328.            push    0               ; semaphore handle (unused
  13329.            push    0               ; by synchronous read)
  13330.            call    DosReadQueue    ; transfer to OS/2
  13331.            or      ax,ax           ; did read succeed?
  13332.            jnz     error           ; jump if function failed
  13333.            les     bx,qmsgptr      ; now let ES:BX point
  13334.                                    ; to queue message
  13335.  
  13336.            .                       ; process the queue
  13337.            .                       ; message here...
  13338.            .
  13339.                                    ; release selector for
  13340.                                    ; queue message...
  13341.            push    ds              ; restore ES = DGROUP
  13342.            pop     es
  13343.            push    word ptr qmsgptr+2 ; selector for message
  13344.            call    DosFreeSeg      ; transfer to OS/2
  13345.            or      ax,ax           ; did release succeed?
  13346.            jnz     error           ; jump if function failed
  13347.  
  13348.                                    ; close the queue,
  13349.                                    ; also destroying it...
  13350.            push    qhandle         ; handle from DosCreateQueue
  13351.            call    DosCloseQueue   ; transfer to OS/2
  13352.            or      ax,ax           ; did close succeed?
  13353.            jnz     error           ; jump if function failed
  13354.            .
  13355.            .
  13356.            .
  13357.    ──────────────────────────────────────────────────────────────────────────
  13358.  
  13359.    Figure 13-11.  Creating a queue, waiting for a message to be written into
  13360.    it, retrieving the message, and then closing the queue (also destroying it
  13361.    because the owner is closing it).
  13362.  
  13363.    DosPeekQueue is similar to DosReadQueue, but the record is not removed
  13364.    from the queue when it is retrieved. Thus, the queue owner can scan the
  13365.    data waiting in the queue and decide on a processing strategy without
  13366.    disturbing the ordering of the records or copying the records to its own
  13367.    memory for inspection. Other, less frequently used queue functions include
  13368.    DosQueryQueue, which allows any process that can supply the handle to a
  13369.    queue to obtain the number of elements currently in the queue, and
  13370.    DosPurgeQueue, which discards all records in a queue. DosPurgeQueue can be
  13371.    invoked only by the queue owner.
  13372.  
  13373.    A process relinquishes its right of access to a queue by calling
  13374.    DosCloseQueue with the queue handle. When the queue owner closes the queue
  13375.    or terminates, the queue is purged, its supporting data structure is
  13376.    destroyed, and any further attempts by other processes to write to the
  13377.    queue return an error. However, the memory segments that contained the
  13378.    queue messages are not destroyed until all processes with valid selectors
  13379.    for the segments release them with DosFreeSeg or terminate.
  13380.  
  13381.  
  13382.  Signals
  13383.  
  13384.    Signals are a unique IPC mechanism: They essentially simulate a hardware
  13385.    interrupt of the receiving process. If a signal occurs and the process has
  13386.    previously indicated its ability to handle that signal type, control
  13387.    transfers immediately to the routine designated as the signal handler.
  13388.    After the handler completes its processing and returns, control returns to
  13389.    the point of interruption.
  13390.  
  13391.    OS/2 has two basic classes of signals. The first class of signals includes
  13392.    those listed in Figure 13-12. The SIGTERM signal results from a call to
  13393.    DosKillProcess by another process; it terminates the receiving process if
  13394.    that process has not registered a handler for it. SIGINTR and SIGBREAK
  13395.    signals go to the last process in a process subtree to register a handler
  13396.    for them. If an application doesn't register its own handler for SIGINTR
  13397.    and SIGBREAK, they are ordinarily fielded by the ancestor command process,
  13398.    which translates them to a SIGTERM signal for all its descendants. A
  13399.    process ignores SIGBROKENPIPE unless a handler has been registered for it.
  13400.  
  13401.    Signals in the second class are known as event flags, and three are
  13402.    available: Flags A, B, and C. A process can send an event flag only to
  13403.    itself or its descendants, and it can send a single word of private data
  13404.    along with the flag. The meaning of the private data is known only to the
  13405.    signaler and signalee; it is ignored by OS/2. If an event flag signal is
  13406.    directed to a process which has not registered a handler for it, the
  13407.    signal is discarded.
  13408.  
  13409.    A process registers a signal handler by calling DosSetSigHandler with the
  13410.    address of the handler, a code selecting one of the seven signals just
  13411.    described, and a code indicating the action to be taken when the signal
  13412.    occurs: ignore the signal, give control to the system's default handler,
  13413.    give control to the process's own handler, reset the current signal
  13414.    without affecting its disposition, or return an error to the process that
  13415.    sent the signal. To register handlers for more than one signal type, a
  13416.    process must make an individual DosSetSigHandler call for each.
  13417.  
  13418.    Signal                    Description
  13419.    ──────────────────────────────────────────────────────────────────────────
  13420.    SIGINTR                   Ctrl-C was detected
  13421.    SIGBREAK                  Ctrl-Break was detected
  13422.    SIGTERM                   Process is being terminated
  13423.    SIGBROKENPIPE             Pipe read or write failed
  13424.    ──────────────────────────────────────────────────────────────────────────
  13425.  
  13426.    Figure 13-12.  OS/2 signals in the first class.
  13427.  
  13428.    A process calls DosFlagProcess to send an event flag signal (A, B, or C)
  13429.    and an arbitrary word of data to itself or to a descendant process, and
  13430.    optionally to the entire subtree of the receiving process. An error is
  13431.    returned if the receiving process has previously registered its refusal to
  13432.    accept the incoming signal type (with a call to DosSetSigHandler). If no
  13433.    error is returned, the destination process either accepted the signal or
  13434.    has not registered a handler for it.
  13435.  
  13436.    On the receiving end, when a signal occurs for which a handler has been
  13437.    registered, the process's primary thread (the thread which received
  13438.    control at the original entry point) is interrupted and control is
  13439.    transferred with a forced far call to the signal handler. If the primary
  13440.    thread is in the middle of an OS/2 call that will not complete quickly
  13441.    (primarily character device I/O calls), that function is aborted and
  13442.    returns with an error. Otherwise, the signal occurs immediately upon the
  13443.    thread's return from the executing function.
  13444.  
  13445.    Because the primary thread can thus be interrupted at any time for an
  13446.    unknown interval, an application that expects to receive signals should
  13447.    reserve the primary thread for that purpose. When the process is started,
  13448.    it can register its signal handlers, create a new thread that becomes the
  13449.    main line of execution for the process, and then block the primary thread
  13450.    on a semaphore.
  13451.  
  13452.    When a signal handler receives control, OS/2 has set up the stack (which
  13453.    is the primary thread's usual stack) with the following contents:
  13454.  
  13455.    SS:SP ───────────── Far return address for handler
  13456.  
  13457.    SS:SP+4 ─────────── Signal number being serviced
  13458.                        1 = Ctrl-C
  13459.                        2 = broken pipe
  13460.                        3 = process termination
  13461.                        4 = Ctrl-break
  13462.                        5 = Flag A
  13463.                        6 = Flag B
  13464.                        7 = Flag C
  13465.  
  13466.    SS:SP+6 ─────────── Private data passed by signaling process if
  13467.                        signal is Flag A, B, or C
  13468.  
  13469.    All registers other than SS, SP, IP, CS, and the CPU flags contain the
  13470.    values they held at the point of the signal's arrival.
  13471.  
  13472.    During signal processing, the handler must call DosSetSigHandler again
  13473.    with action code 4 (reset current signal); if it does not do so, further
  13474.    signals of that type will not be serviced. The handler exits with a far
  13475.    return, clearing the stack of the additional two words containing the
  13476.    signal number and the signal argument. OS/2 then restores all register
  13477.    contents and returns control to the point of interruption. Alternatively,
  13478.    the handler can reset the stack frame to some desired state and branch
  13479.    directly to another point in the program; that is, no harm is done if the
  13480.    handler never returns. Skeleton code for MASM and C signal handlers
  13481.    appears in Figures 13-13 and 13-14.
  13482.  
  13483.    You can suppress signal handling temporarily with DosHoldSignal so that
  13484.    critical sections of code or "slow" OS/2 calls are not interrupted. The
  13485.    signals that occur while a DosHoldSignal is in effect are postponed and
  13486.    not lost. Naturally, you should postpone signals for as short a period as
  13487.    possible── treat them as though they were hardware interrupts being
  13488.    disabled.
  13489.  
  13490.    ──────────────────────────────────────────────────────────────────────────
  13491.    SIGINTR equ     1               ; Ctrl-C signal number
  13492.  
  13493.    prvact  dw      ?               ; receives previous action
  13494.  
  13495.    prvhdlr dd      ?               ; receives address of
  13496.                                    ; previous signal handler
  13497.  
  13498.    dummy   dd      ?               ; dummy storage for type 4
  13499.                                    ; DosSetSigHandler call
  13500.  
  13501.            .
  13502.            .
  13503.            .
  13504.                                    ; register signal handler...
  13505.            push    cs              ; address of new handler
  13506.            push    offset _TEXT:handler
  13507.            push    ds              ; receives address of
  13508.                                    ; previous handler
  13509.            push    offset DGROUP:prvhdlr
  13510.            push    ds              ; receives previous action
  13511.            push    offset DGROUP:prvact
  13512.            push    2               ; action = 2: call handler
  13513.            push    SIGINTR         ; signal of interest = Ctrl-C
  13514.            call    DosSetSigHandler ; transfer to OS/2
  13515.            or      ax,ax           ; was handler registered?
  13516.            jnz     error           ; jump if function failed
  13517.            .
  13518.            .
  13519.            .
  13520.    handler proc    far             ; handler for Ctrl-C signal
  13521.  
  13522.            .                       ; application-specific
  13523.            .                       ; signal processing here...
  13524.            .
  13525.                                    ; reset signal so another
  13526.                                    ; can be processed...
  13527.            push    cs              ; address of handler
  13528.            push    offset _TEXT:handler
  13529.            push    ds              ; previous handler
  13530.            push    offset DGROUP:dummy
  13531.            push    ds              ; previous action
  13532.            push    offset DGROUP:dummy
  13533.            push    4               ; action 4 = reset signal
  13534.            push    SIGINTR         ; signal of interest = Ctrl-C
  13535.            call    DosSetSigHandler ; transfer to OS/2
  13536.  
  13537.            ret     4               ; return from handler
  13538.                                    ; and clear stack
  13539.    handler endp
  13540.    ──────────────────────────────────────────────────────────────────────────
  13541.  
  13542.    Figure 13-13.  Registration of a MASM handler for Ctrl-C signals, along
  13543.    with a skeleton for the handler itself.
  13544.  
  13545.    ──────────────────────────────────────────────────────────────────────────
  13546.    #define SIGINTR 1               /* Ctrl-C signal number */
  13547.  
  13548.                                    /* function prototypes */
  13549.    void far pascal handler(unsigned, int);
  13550.  
  13551.    unsigned extern far pascal DosSetSigHandler(void (pascal far *)
  13552.    (unsigned, int) unsigned long far *, unsigned far *, int, int);
  13553.  
  13554.    unsigned extern far pascal DosSleep(unsigned long);
  13555.  
  13556.    main()
  13557.    {
  13558.        unsigned long prevh;        /* prev. handler address */
  13559.        unsigned preva;             /* prev. handler action */
  13560.        .
  13561.        .
  13562.        .
  13563.                                    /* register handler */
  13564.        DosSetSigHandler(handler, &prevh, &preva, 2, SIGINTR);
  13565.        .
  13566.        .
  13567.        .
  13568.    }
  13569.  
  13570.  
  13571.    /*
  13572.        This is the actual signal handler for Ctrl-C.
  13573.    */
  13574.  
  13575.    void far pascal handler(unsigned sigarg, int signum)
  13576.    {
  13577.        unsigned long dummy1;       /* scratch variables */
  13578.        unsigned dummy2;
  13579.  
  13580.                                    /* reset signal */
  13581.        DosSetSigHandler(handler, &dummy1, &dummy2, 4, SIGINTR);
  13582.  
  13583.                                    /* announce signal */
  13584.        fprintf(stderr,"\nCtrl-C Signal Received!\n");
  13585.    }
  13586.    ──────────────────────────────────────────────────────────────────────────
  13587.  
  13588.    Figure 13-14.  Registration of a C handler for Ctrl-C signals, along with
  13589.    a skeleton for the handler itself.
  13590.  
  13591.  
  13592.  
  13593.  ────────────────────────────────────────────────────────────────────────────
  13594.  Chapter 14  IOPL Segments
  13595.  
  13596.    As a single-user multitasking operating system, OS/2 adheres to a
  13597.    philosophy of protection that is vastly different than that of its
  13598.    multi-user cousins. Because users assume responsibility both for the
  13599.    programs they install on their systems and (if necessary) for securing
  13600.    physical access to the system, OS/2 allows applications a degree of
  13601.    freedom that would be unthinkable in a multi-user environment such as
  13602.    UNIX/XENIX. For example, programs can generate machine code dynamically in
  13603.    a data segment and then execute it with the aid of DosCreateCSAlias, and
  13604.    they can filter the raw data stream of the keyboard, mouse, and printer
  13605.    drivers with the device monitor API. They can even read or write I/O ports
  13606.    without relying on a device driver.
  13607.  
  13608.    The ability to bypass OS/2 and access the I/O ports of an adapter directly
  13609.    is particularly important for developers of graphics applications that are
  13610.    not written to run in a Presentation Manager window or that rely on video
  13611.    modes or capabilities that are not supported by OS/2's built-in display
  13612.    functions. OS/2 places only three constraints on such programs:
  13613.  
  13614.    ■  They may not service interrupts. (Control of the interrupt system is
  13615.       reserved for the kernel and true device drivers.)
  13616.  
  13617.    ■  The hardware-dependent code must be segregated into special segments
  13618.       called IOPL segments.
  13619.  
  13620.    ■  The code running in an IOPL (I/O Privilege Level) segment can call only
  13621.       a restricted set of API functions.
  13622.  
  13623.    An application that manipulates the video adapter directly must also, of
  13624.    course, cooperate with OS/2 to save and restore the screen and adapter
  13625.    state across session switches.
  13626.  
  13627.    Dynlink libraries can contain IOPL segments, which execute on behalf of
  13628.    the calling process. A dynlink library can therefore act much like a
  13629.    device driver from an application's perspective, concealing hardware
  13630.    characteristics inside a set of routines with a formalized
  13631.    parameter-passing scheme and named entry points. The OS/2 video subsystem,
  13632.    for example, is implemented primarily as dynlink libraries.
  13633.  
  13634.  
  13635.  What Is IOPL?
  13636.  
  13637.    Whereas segment selectors and their associated descriptors govern memory
  13638.    addressability for a protected mode process, as discussed in Chapter 11,
  13639.    the behavior of the process is also constrained by the 80286's support for
  13640.    four privilege levels──rings 0, 1, 2, and 3. Programs running at ring 0
  13641.    have unrestricted access to the hardware, including the ability to execute
  13642.    any instruction, to read or write any I/O port, and to manipulate the
  13643.    special registers and tables that control memory protection and virtual
  13644.    memory.
  13645.  
  13646.    Rings 1 through 3 are intended for the execution of progressively "less
  13647.    trusted" programs. Transitions from one ring to another are strictly
  13648.    controlled by means of call gates, which can be set up only by a program
  13649.    that is running at ring 0. Programs in ring 1, 2, or 3 cannot execute
  13650.    certain instructions that would compromise memory protection, nor can they
  13651.    touch memory segments for which they have inadequate access rights.
  13652.    (Included in the descriptor for each memory segment is an indication of
  13653.    the privilege level required for using that segment's selector.)
  13654.  
  13655.    Nevertheless, programs in ring 1, 2, or 3 can gain restricted access to
  13656.    the hardware depending on the value of the IOPL field in the CPU flags
  13657.    register. Whenever a program is running in a ring whose number is less
  13658.    than or equal to the contents of the IOPL field, it can use the six
  13659.    instructions IN, OUT, INS, OUTS, STI, and CLI without causing a protection
  13660.    fault. Needless to say, the IOPL field itself can be modified only by a
  13661.    program running at ring 0.
  13662.  
  13663.    Under OS/2, ring 0 is reserved for the operating system kernel and its
  13664.    device drivers. Ring 1 is not used at all, and normal application code
  13665.    runs at ring 3. After system initialization, OS/2 sets the IOPL level to 2
  13666.    and uses ring 2 only for the execution of code within application IOPL
  13667.    segments (Figure 14-1). A program must declare at link time the names of
  13668.    its IOPL segments, along with the names of the routines within them which
  13669.    will be accessible to the rest of the application.
  13670.  
  13671.    ┌────────────────────────────────────┐
  13672.    │                                    │
  13673.    │   Figure 14-1 can be found on      │
  13674.    │   page 303 of the printed manual.  │
  13675.    │                                    │
  13676.    └────────────────────────────────────┘
  13677.  
  13678.    Figure 14-1.  Use of the four 80286 privilege levels under OS/2.
  13679.  
  13680.    When an application containing an IOPL segment is loaded, OS/2 sets up
  13681.    call gates for each "exported" routine in that segment, and it resolves
  13682.    the references to those routines throughout the program so that they point
  13683.    to the call gate. When the program's mainline code, which is running at
  13684.    ring 3, calls a routine in an IOPL segment, the hardware traps the
  13685.    reference to the call gate. The CPU then changes the privilege level to
  13686.    ring 2, switches to a new stack, copies parameters from the old stack to
  13687.    the new one, and enters the IOPL routine. When the IOPL routine exits with
  13688.    a far return, the system restores the original privilege level and stack,
  13689.    clears the parameters from the caller's stack, and continues execution.
  13690.  
  13691.    Application programs with IOPL, like MS-DOS programs running in DOS
  13692.    compatibility mode, are clearly a weak point in system security. One can
  13693.    easily imagine, for example, an OS/2 protected mode Trojan horse program
  13694.    that would masquerade as a public domain utility but would use an IOPL
  13695.    segment to take over the disk controller and destroy the disk directories
  13696.    and file allocation table. To prevent such disasters, OS/2 can be
  13697.    configured so that it will not allow execution of MS-DOS programs or
  13698.    protected mode programs requiring IOPL. If the CONFIG.SYS file contains
  13699.    the statement PROTECTONLY=YES, MS-DOS applications are not supported; if
  13700.    the statement IOPL=NO is present, applications with IOPL segments are not
  13701.    loaded.
  13702.  
  13703.  
  13704.  API Functions and IOPL
  13705.  
  13706.    In OS/2 version 1.0, code running in an IOPL segment cannot call kernel
  13707.    API functions; all API calls must be made from ring 3 code. This
  13708.    restriction is loosened somewhat in OS/2 version 1.1, which permits code
  13709.    executing at ring 2 to call a limited number of API functions directly.
  13710.    These are known as "conforming" functions because they have a special call
  13711.    gate that lets them take on the privilege level of the caller. The
  13712.    conforming functions are listed in Figure 14-2 and are marked with an
  13713.    [IOPL] icon in the reference section.
  13714.  
  13715.    Conforming Functions under OS/2 Version 1.1
  13716.    ──────────────────────────────────────────────────────────────────────────
  13717.    DosAllocHuge            DosGetHugeShift        DosRead
  13718.    DosAllocSeg             DosGetInfoSeg          DosReadAsync
  13719.    DosAllocShrSeg          DosGetMachineMode      DosReallocHuge
  13720.    DosBeep                 DosGetModHandle        DosReallocSeg
  13721.    DosBufReset             DosGetModName          DosResumeThread
  13722.    DosCallback             DosGetPID              DosRmDir
  13723.    DosChDir                DosGetPPID             DosScanEnv
  13724.    DosChgFilePtr           DosGetProcAddr         DosSearchPath
  13725.    DosCLIAccess            DosGetPrty             DosSelectDisk
  13726.    DosClose                DosGetResource         DosSemClear
  13727.    DosCloseSem             DosGetSeg              DosSemRequest
  13728.    DosCreateCSAlias        DosGetShrSeg           DosSemSet
  13729.    DosCreateSem            DosGetVersion          DosSemSetWait
  13730.    DosCreateThread         DosGiveSeg             DosSemWait
  13731.    DosCwait                DosHoldSignal          DosSendSignal
  13732.    DosDelete               DosKillProcess         DosSetCp
  13733.    DosDevConfig            DosLoadModule          DosSetDateTime
  13734.    DosDevIOCtl             DosLockSeg             DosSetFHandState
  13735.    DosDupHandle            DosMakePipe            DosSetFileInfo
  13736.    DosEnterCritSec         DosMemAvail            DosSetFileMode
  13737.    DosErrClass             DosMkDir               DosSetFSInfo
  13738.    DosError                DosMove                DosSetMaxFH
  13739.    DosExecPgm              DosMuxSemWait          DosSetPrty
  13740.    DosExit                 DosNewSize             DosSetSigHandler
  13741.    DosExitCritSec          DosOpen                DosSetVec
  13742.    DosExitList             DosOpenSem             DosSetVerify
  13743.    DosFileLocks            DosPhysicalDisk        DosSizeSeg
  13744.    DosFindClose            DosPortAccess          DosSleep
  13745.    DosFindFirst            DosPTrace              DosSubAlloc
  13746.    DosFindNext             DosQAppType            DosSubFree
  13747.    DosFlagProcess          DosQCurDir             DosSubSet
  13748.    DosFreeModule           DosQCurDisk            DosSuspendThread
  13749.    DosFreeSeg              DosQFHandState         DosTimerAsync
  13750.    DosFSRamSemClear        DosQFileInfo           DosTimerStart
  13751.    DosFSRamSemRequest      DosQFileMode           DosTimerStop
  13752.    DosGetCp                DosQFSInfo             DosUnlockSeg
  13753.    DosGetDateTime          DosQHandType           DosWrite
  13754.    DosGetEnv               DosQVerify             DosWriteAsync
  13755.    ──────────────────────────────────────────────────────────────────────────
  13756.  
  13757.    Figure 14-2.  Kernel API functions that can be called by code executing
  13758.    within an IOPL segment.
  13759.  
  13760.    Four API functions are present specifically for the needs of IOPL
  13761.    applications: DosCLIAccess, DosPortAccess, DosR2StackRealloc, and
  13762.    DosCallback. DosCLIAccess informs the operating system that the
  13763.    application needs to execute the CLI and STI instructions to disable and
  13764.    enable interrupts, or that it no longer needs to use those instructions.
  13765.    Similarly, DosPortAccess notifies the operating system that the
  13766.    application requires (or no longer requires) access to a range of I/O
  13767.    ports. A call to DosPortAccess also implicitly requests the use of CLI and
  13768.    STI, so an additional call to DosCLIAccess is not required.
  13769.  
  13770.    In the current versions of OS/2, neither DosPortAccess nor DosCLIAccess
  13771.    does anything; an application with I/O privilege can read or write I/O
  13772.    ports and execute CLI or STI whether it calls these functions or not.
  13773.    DosPortAccess and DosCLIAccess are present for upward compatibility with
  13774.    future, 80386-specific versions of the operating system. The 80386 allows
  13775.    access to individual I/O ports to be controlled on a per-process basis by
  13776.    means of an I/O permissions bitmap associated with each task state segment
  13777.    (TSS).
  13778.  
  13779.    DosCallback and DosR2StackRealloc became available in OS/2 version 1.1.
  13780.    DosR2StackRealloc allows a thread to increase (but not to decrease) the
  13781.    size of its ring 2 stack, that is, the stack that it uses while executing
  13782.    within an IOPL segment. The default size of the stack allocated by OS/2 is
  13783.    512 bytes, which is not large enough for routines that are heavily
  13784.    recursive or that call the kernel API (in which case Microsoft recommends
  13785.    a minimum stack size of 2 KB).
  13786.  
  13787.    The function DosCallback is, in effect, a call gate implemented with
  13788.    software that allows a ring 2 routine to call ring 3 procedures in the
  13789.    same application. Because a stack switch is involved──and DosCallback does
  13790.    not provide for the copying of any stack items──your program must use
  13791.    registers to pass any parameters for the ring 3 code. If the ring 2 and
  13792.    ring 3 routines communicate through variables, those variables must not
  13793.    lie in a ring 2 data segment (that is, a data segment that has been marked
  13794.    IOPL in the module definition file).
  13795.  
  13796.  
  13797.  Using IOPL in OS/2 Applications
  13798.  
  13799.    If you follow a few simple rules, you will find that designing and coding
  13800.    an OS/2 application that uses IOPL segments is a straightforward process.
  13801.  
  13802.    First, you must segregate from the rest of the application code all code
  13803.    that needs to enable or disable interrupts or to access I/O ports. This
  13804.    distinct segment becomes the IOPL segment. For compatibility with
  13805.    Microsoft C, give the segment the WORD and PUBLIC attributes and assign it
  13806.    to class 'CODE'. Be sure to choose a segment name that won't conflict with
  13807.    the standard Microsoft segment and group names (_TEXT, _DATA, DGROUP,
  13808.    and so forth).
  13809.  
  13810.    Second, for the procedures in the IOPL segment to be usable from a high
  13811.    level language, they should follow the OS/2 API conventions of accepting
  13812.    their parameters on the stack and returning their results in register AX
  13813.    or in variables whose addresses were passed in the original call. You must
  13814.    declare the procedures that contain the hardware-dependent code public and
  13815.    give them the far attribute because they will be entered through a call
  13816.    gate and must exit with a far return. The parameter to the RET instruction
  13817.    must be the number of bytes (not words) to be cleared from the caller's
  13818.    stack.
  13819.  
  13820.    If you are writing the body of your application in C, supply extern far
  13821.    pascal prototypes for the external IOPL routines, just as with API
  13822.    functions. This tells the C compiler that parameters are pushed left to
  13823.    right, that the called routine clears the stack, that a leading underscore
  13824.    should not be added to the external name, and that case in the external
  13825.    name can be ignored. If you are writing a MASM application, simply declare
  13826.    the IOPL routines as extrn far in the usual manner.
  13827.  
  13828.    Third, the initialization and termination portions of your application
  13829.    should include a call to the kernel API function DosPortAccess or
  13830.    DosCLIAccess. Use of these functions in the documented manner ensures that
  13831.    your application will still work as expected when the 80386-specific
  13832.    versions of OS/2 appear.
  13833.  
  13834.    Last, when you build the executable application, you must provide the
  13835.    Linker with a module definition (DEF) file which contains a SEGMENTS
  13836.    directive for the IOPL segment and an EXPORTS directive that declares the
  13837.    name and number of stack parameters for each routine in the IOPL segment
  13838.    that will be called from outside the segment. If you are writing your
  13839.    program in C, you need to compile and link in separate operations because
  13840.    the C compiler does not know how to pass the name of a module definition
  13841.    file through to the Linker.
  13842.  
  13843.    Take special care that the EXPORTS directive matches the actual code in
  13844.    the corresponding IOPL routine. If too few parameters are specified in the
  13845.    EXPORTS directive, the parameters are not all copied to the ring 2 stack,
  13846.    and the IOPL routine will generate a protect fault when it tries to access
  13847.    one of the missing items. If too many parameters are specified in the
  13848.    EXPORTS directive, no harm is done unless the operand for the IOPL
  13849.    routine's RET instruction is also incorrect. If that operand is also too
  13850.    large, the system clears too many items from the caller's stack when the
  13851.    IOPL routine exits, and a protect fault is likely to occur at a later
  13852.    point in the program's execution.
  13853.  
  13854.  
  13855.  A Sample IOPL Application
  13856.  
  13857.    The program PORTS.EXE provides a simple and practical demonstration of an
  13858.    OS/2 application that uses direct hardware access to read and display the
  13859.    first 256 I/O ports. PORTS.EXE is built from three modules: PORTS.C or
  13860.    PORTS.ASM, PORTIO.ASM, and PORTS.DEF.
  13861.  
  13862.    PORTIO.ASM (Figure 14-3) is the program's IOPL segment. It contains only
  13863.    two routines: rport and wport. The rport routine accepts a port address,
  13864.    reads the port, and returns the port data register in AX with the upper
  13865.    eight bits zeroed. The wport routine accepts a port address and a word of
  13866.    data, writes the lower eight bits of the data to the port, and returns
  13867.    nothing. Both routines use the OS/2 API conventions and can be called from
  13868.    either MASM or C. Notice that the code segment in this file is called
  13869.    IO_TEXT to differentiate it from the code segment produced by the C
  13870.    compiler (which has the default name _TEXT).
  13871.  
  13872.    PORTS.C (Figure 14-4 on p. 309) is the C version of the body of the
  13873.    application. It invokes DosPortAccess to request access to a range of I/O
  13874.    ports; then it calls rport to read each port and formats the data for
  13875.    display with printf(). Before terminating, the program again calls
  13876.    DosPortAccess to release the I/O ports requested earlier. PORTS.ASM
  13877.    (Figure 14-5 on p. 311) is the MASM equivalent of PORTS.C.
  13878.  
  13879.    ──────────────────────────────────────────────────────────────────────────
  13880.            title   PORTIO -- Read/Write I/O Ports
  13881.            page    55,132
  13882.            .286
  13883.  
  13884.    ;
  13885.    ; PORTIO.ASM
  13886.    ;
  13887.    ; General-purpose port read/write routines for C or MASM programs.
  13888.    ;
  13889.    ; Assemble with: C> masm portio.asm;
  13890.    ;
  13891.    ; When this module is linked into a program, the following lines
  13892.    ; must be present in the program's module definition (DEF) file:
  13893.    ;
  13894.    ; SEGMENTS
  13895.    ;   IO_TEXT IOPL
  13896.    ;
  13897.    ; EXPORTS
  13898.    ;   RPORT 1
  13899.    ;   WPORT 2
  13900.    ;
  13901.    ; The SEGMENTS and EXPORTS directives are recognized by the Linker
  13902.    ; and cause information to be built into the EXE file header for
  13903.    ; the OS/2 program loader. The loader is signaled to give I/O
  13904.    ; privilege to code executing in the segment IO_TEXT and to build
  13905.    ; call gates for the routines 'rport' and 'wport'.
  13906.    ;
  13907.    ; Copyright (C) 1988 Ray Duncan
  13908.    ;
  13909.  
  13910.    IO_TEXT segment word public 'CODE'
  13911.  
  13912.            assume  cs:IO_TEXT
  13913.  
  13914.  
  13915.    ; RPORT: Read 8-bit data from I/O port. Port address
  13916.    ; is passed on stack; data is returned in register AX
  13917.    ; with AH zeroed. Other registers are unchanged.
  13918.    ;
  13919.    ; C syntax:     unsigned port, data;
  13920.    ;               data = rport(port);
  13921.  
  13922.            public  rport
  13923.    rport   proc    far
  13924.  
  13925.            push    bp              ; save registers and
  13926.            mov     bp,sp           ; set up stack frame
  13927.            push    dx
  13928.  
  13929.            mov     dx,[bp+6]       ; get port number
  13930.            in      al,dx           ; read the port
  13931.            xor     ah,ah           ; clear upper 8 bits
  13932.  
  13933.            pop     dx              ; restore registers
  13934.            pop     bp
  13935.  
  13936.            ret     2               ; discard parameters,
  13937.                                    ; return port data in AX
  13938.    rport   endp
  13939.    ; WPORT: Write 8-bit data to I/O port. Port address and
  13940.    ; data are passed on stack. All registers are unchanged.
  13941.    ;
  13942.    ; C syntax:     unsigned port, data;
  13943.    ;               wport(port, data);
  13944.  
  13945.            public  wport
  13946.    wport   proc    far
  13947.  
  13948.            push    bp              ; save registers and
  13949.            mov     bp,sp           ; set up stack frame
  13950.            push    ax
  13951.            push    dx
  13952.  
  13953.            mov     ax,[bp+6]       ; get data to write
  13954.            mov     dx,[bp+8]       ; get port number
  13955.            out     dx,al           ; write the port
  13956.  
  13957.            pop     dx              ; restore registers
  13958.            pop     ax
  13959.            pop     bp
  13960.  
  13961.            ret     4               ; discard parameters,
  13962.                                    ; return nothing
  13963.    wport   endp
  13964.  
  13965.    IO_TEXT ends
  13966.  
  13967.            end
  13968.    ──────────────────────────────────────────────────────────────────────────
  13969.  
  13970.    Figure 14-3.  PORTIO.ASM, the source code for the IOPL segment which
  13971.    contains the routines to read and write I/O ports.
  13972.  
  13973.    ──────────────────────────────────────────────────────────────────────────
  13974.    /*
  13975.            PORTS.C
  13976.            An OS/2 IOPL demonstration program that reads and displays the
  13977.            first 256 I/O ports. Requires the separate module PORTIO.ASM.
  13978.  
  13979.            Compile with:  C> cl /c ports.c
  13980.            Link with:  C> link ports+portio,ports,,os2,ports
  13981.            Usage is:  C> ports
  13982.  
  13983.            Copyright (C) 1988 Ray Duncan
  13984.    */
  13985.  
  13986.    #include <stdio.h>
  13987.  
  13988.    #define API extern far pascal
  13989.  
  13990.    unsigned API rport(unsigned);           /* function prototypes */
  13991.    void     API wport(unsigned, unsigned);
  13992.    void     API DosSleep(unsigned long);
  13993.    unsigned API DosPortAccess(unsigned, unsigned, unsigned, unsigned);
  13994.  
  13995.                                    /* parameters for DosPortAccess */
  13996.    #define REQUEST 0                       /* request port */
  13997.    #define RELEASE 1                       /* release port */
  13998.    #define BPORT   0                       /* beginning port */
  13999.    #define EPORT   255                     /* ending port */
  14000.  
  14001.    main(int argc, char *argv[])
  14002.    {
  14003.        int i;                              /* scratch variable */
  14004.  
  14005.                                            /* request port access */
  14006.        if(DosPortAccess(0, REQUEST, BPORT, EPORT))
  14007.        {
  14008.            printf("\nDosPortAccess failed.\n");
  14009.            exit(1);
  14010.        }
  14011.  
  14012.        printf("\n      ");                 /* print title line */
  14013.        for(i=0; i<16; i++) printf(" %2X", i);
  14014.  
  14015.        for(i=BPORT; i<=EPORT; i++)         /* loop through all ports */
  14016.        {
  14017.            if((i & 0x0f)==0)
  14018.            {
  14019.                printf("\n%04X  ", i);      /* new line needed */
  14020.            }
  14021.  
  14022.            printf(" %02X", rport(i));      /* read and display port */
  14023.        }
  14024.  
  14025.        DosPortAccess(0, RELEASE, BPORT, EPORT);  /* release port access */
  14026.    }
  14027.    ──────────────────────────────────────────────────────────────────────────
  14028.  
  14029.    Figure 14-4.  PORTS.C, the C source code for the body of the PORTS.EXE
  14030.    example program.
  14031.  
  14032.    ──────────────────────────────────────────────────────────────────────────
  14033.            title   PORTS -- IOPL Demo Program
  14034.            page    55,132
  14035.            .286
  14036.  
  14037.    ;
  14038.    ; PORTS.ASM
  14039.    ;
  14040.    ; An OS/2 IOPL demonstration program that reads and displays the
  14041.    ; first 256 I/O ports. Requires the separate module PORTIO.ASM.
  14042.    ;
  14043.    ; Assemble with:  C> masm ports.asm;
  14044.    ; Link with:  C> link ports+portio,ports,,os2,ports
  14045.    ;
  14046.    ; Usage is:  C> ports
  14047.    ;
  14048.    ; Copyright (C) 1988 Ray Duncan
  14049.    ;
  14050.  
  14051.    cr      equ     0dh                     ; ASCII carriage return
  14052.    lf      equ     0ah                     ; ASCII linefeed
  14053.    blank   equ     20h                     ; ASCII space code
  14054.  
  14055.    stdout  equ     1                       ; standard output handle
  14056.    stderr  equ     2                       ; standard error handle
  14057.  
  14058.    bport   equ     0                       ; first port to display
  14059.    eport   equ     255                     ; last port to display
  14060.    request equ     0                       ; request port access
  14061.    release equ     1                       ; release port access
  14062.  
  14063.            extrn   DosPortAccess:far       ; kernel API functions
  14064.            extrn   DosWrite:far
  14065.            extrn   DosExit:far
  14066.  
  14067.            extrn   rport:far               ; PORTIO.ASM functions
  14068.            extrn   wport:far
  14069.  
  14070.    DGROUP  group   _DATA
  14071.  
  14072.    _DATA   segment word public 'DATA'
  14073.  
  14074.    wlen    dw      0                       ; actual number of bytes
  14075.                                            ; written by DosWrite
  14076.    msg1    db      cr,lf                   ; error message
  14077.            db      'DosPortAccess failed.'
  14078.            db      cr,lf
  14079.    msg1_len equ    $-msg1
  14080.  
  14081.    msg2    db      cr,lf                   ; heading
  14082.            db      '        0  1  2  3  4  5  6'
  14083.            db      '  7  8  9  A  B  C  D  E  F'
  14084.    msg2_len equ    $-msg2
  14085.  
  14086.    msg3    db      cr,lf                   ; display port number
  14087.    msg3a   db      'NNNN  '
  14088.    msg3_len equ    $-msg3
  14089.  
  14090.    msg4    db      ' '                     ; display port data
  14091.    msg4a   db      'NN'
  14092.    msg4_len equ    $-msg4
  14093.  
  14094.    _DATA   ends
  14095.  
  14096.  
  14097.    _TEXT   segment word public 'CODE'
  14098.  
  14099.            assume  cs:_TEXT,ds:DGROUP
  14100.  
  14101.    main    proc    far                     ; entry point from OS/2
  14102.  
  14103.            push    ds                      ; make DGROUP addressable
  14104.            pop     es                      ; with ES too
  14105.  
  14106.                                            ; request port access...
  14107.            push    0                       ; reserved
  14108.            push    request                 ; request/release
  14109.            push    bport                   ; first port number
  14110.            push    eport                   ; last port number
  14111.            call    DosPortAccess           ; transfer to OS/2
  14112.            or      ax,ax                   ; call successful?
  14113.            jz      main1                   ; yes, jump
  14114.  
  14115.                                            ; display error message...
  14116.            push    stderr                  ; standard error handle
  14117.            push    ds                      ; message address
  14118.            push    offset DGROUP:msg1
  14119.            push    msg1_len                ; message length
  14120.            push    ds                      ; receives bytes written
  14121.            push    offset DGROUP:wlen
  14122.            call    DosWrite                ; transfer to OS/2
  14123.                                            ; now terminate process...
  14124.            push    1                       ; terminate all threads
  14125.            push    1                       ; return code = 1 (error)
  14126.            call    DosExit                 ; transfer to OS/2
  14127.  
  14128.    main1:                                  ; print heading...
  14129.            push    stdout                  ; standard output handle
  14130.            push    ds                      ; address of heading
  14131.            push    offset DGROUP:msg2
  14132.            push    msg2_len                ; length of heading
  14133.            push    ds                      ; receives bytes written
  14134.            push    offset DGROUP:wlen
  14135.            call    DosWrite                ; transfer to OS/2
  14136.  
  14137.            mov     dx,bport                ; initialize port number
  14138.  
  14139.    main2:  test    dx,0fh                  ; new line needed?
  14140.            jnz     main3                   ; no, jump
  14141.  
  14142.            mov     ax,dx                   ; convert port number
  14143.            mov     di,offset DGROUP:msg3a  ; to ASCII for display
  14144.            call    wtoa
  14145.  
  14146.                                            ; display port number...
  14147.            push    stdout                  ; standard output handle
  14148.            push    ds                      ; message address
  14149.            push    offset DGROUP:msg3
  14150.            push    msg3_len                ; message length
  14151.            push    ds                      ; receives bytes written
  14152.            push    offset DGROUP:wlen
  14153.            call    DosWrite                ; transfer to OS/2
  14154.  
  14155.    main3:  push    dx                      ; call 'rport' to read port
  14156.            call    rport                   ; returns AX = data
  14157.  
  14158.            mov     di,offset msg4a         ; convert port data
  14159.            call    btoa                    ; to ASCII for display
  14160.  
  14161.                                            ; display port data...
  14162.            push    stdout                  ; standard output handle
  14163.            push    ds                      ; message address
  14164.            push    offset DGROUP:msg4
  14165.            push    msg4_len                ; message length
  14166.            push    ds                      ; receives bytes written
  14167.            push    offset DGROUP:wlen
  14168.            call    DosWrite                ; transfer to OS/2
  14169.            inc     dx                      ; increment port number
  14170.            cmp     dx,eport                ; done with all ports?
  14171.            jbe     main2                   ; not yet, read another
  14172.  
  14173.                                            ; release port access...
  14174.            push    0                       ; reserved
  14175.            push    release                 ; request/release
  14176.            push    bport                   ; first port number
  14177.            push    eport                   ; last port number
  14178.            call    DosPortAccess           ; transfer to OS/2
  14179.  
  14180.                                            ; final exit to OS/2...
  14181.            push    1                       ; terminate all threads
  14182.            push    0                       ; return code = 0 (success)
  14183.            call    DosExit                 ; transfer to OS/2
  14184.  
  14185.    main    endp
  14186.  
  14187.    ; WTOA:         Convert word to hex ASCII
  14188.    ;
  14189.    ; Call with:    AX    = data to convert
  14190.    ;               ES:DI = storage address
  14191.    ; Returns:      nothing
  14192.    ; Uses:         AX, CL, DI
  14193.  
  14194.    wtoa    proc    near
  14195.  
  14196.            push    ax                      ; save original value
  14197.            mov     al,ah
  14198.            call    btoa                    ; convert upper byte
  14199.  
  14200.            pop     ax                      ; restore original value
  14201.            call    btoa                    ; convert lower byte
  14202.  
  14203.            ret                             ; back to caller
  14204.  
  14205.    wtoa    endp
  14206.  
  14207.  
  14208.    ; BTOA:         Convert byte to hex ASCII
  14209.    ;
  14210.    ; Call with:    AL    = data to convert
  14211.    ;               ES:DI = storage address
  14212.    ; Returns:      nothing
  14213.    ; Uses:         AX, CL, DI
  14214.    btoa    proc    near
  14215.  
  14216.            sub     ah,ah                   ; clear upper byte
  14217.  
  14218.            mov     cl,16                   ; divide by 16
  14219.            div     cl
  14220.  
  14221.            call    ascii                   ; convert quotient
  14222.            stosb                           ; store ASCII character
  14223.  
  14224.            mov     al,ah
  14225.            call    ascii                   ; convert remainder
  14226.            stosb                           ; store ASCII character
  14227.  
  14228.            ret                             ; back to caller
  14229.  
  14230.    btoa    endp
  14231.  
  14232.  
  14233.    ; ASCII:        Convert nibble to hex ASCII
  14234.    ;
  14235.    ; Call with:    AL    = data to convert in low 4 bits
  14236.    ; Returns:      AL    = ASCII character
  14237.    ; Uses:         nothing
  14238.  
  14239.    ascii   proc    near
  14240.  
  14241.            add     al,'0'                  ; add base ASCII value
  14242.            cmp     al,'9'                  ; is it in range 0 through 9?
  14243.            jle     ascii2                  ; jump if it is
  14244.  
  14245.            add     al,'A'-'9'-1            ; no, adjust for range A-F
  14246.  
  14247.    ascii2: ret                             ; return ASCII character in AL
  14248.  
  14249.    ascii   endp
  14250.  
  14251.    _TEXT   ends
  14252.  
  14253.            end     main                    ; defines entry point
  14254.    ──────────────────────────────────────────────────────────────────────────
  14255.  
  14256.    Figure 14-5.  PORTS.ASM, the MASM source code for the body of the
  14257.    PORTS.EXE example program.
  14258.  
  14259.    PORTS.DEF (Figure 14-6 on the following page) is the module definition
  14260.    file. The SEGMENTS directive marks IO_TEXT as an IOPL segment. The EXPORTS
  14261.    directive defines the number of stack parameters for RPORT and WPORT and
  14262.    identifies the two routines as callable from outside the IOPL segment.
  14263.  
  14264.    ──────────────────────────────────────────────────────────────────────────
  14265.    NAME PORTS WINDOWCOMPAT
  14266.    PROTMODE
  14267.    STACKSIZE 4096
  14268.    SEGMENTS
  14269.      IO_TEXT IOPL
  14270.    EXPORTS
  14271.      RPORT 1
  14272.      WPORT 2
  14273.    ──────────────────────────────────────────────────────────────────────────
  14274.  
  14275.    Figure 14-6.  PORTS.DEF, the module definition file for the PORTS.EXE
  14276.    demonstration program. Omit the STACKSIZE directive if you are linking the
  14277.    C version.
  14278.  
  14279.    To build the executable program file PORTS.EXE from the source files
  14280.    PORTS.C, PORTIO.ASM, and PORTS.DEF, enter the following commands:
  14281.  
  14282.    [C:\] CL /c PORTS.C  <Enter>
  14283.    [C:\] MASM PORTIO.ASM;  <Enter>
  14284.    [C:\] LINK PORTS+PORTIO,PORTS,,OS2,PORTS  <Enter>
  14285.  
  14286.    To build PORTS.EXE from the source files PORTS.ASM, PORTIO.ASM, and
  14287.    PORTS.DEF, enter the commands:
  14288.  
  14289.    [C:\] MASM PORTS.ASM;  <Enter>
  14290.    [C:\] MASM PORTIO.ASM;  <Enter>
  14291.    [C:\] LINK PORTS+PORTIO,PORTS,,OS2,PORTS  <Enter>
  14292.  
  14293.    Figure 14-7 shows a sample of the output for PORTS.EXE. If you get the
  14294.    error message OS/2 is not presently configured to run this application,
  14295.    add the statement IOPL=YES to your CONFIG.SYS file and reboot the system.
  14296.  
  14297.    ──────────────────────────────────────────────────────────────────────────
  14298.    [C:\] PORTS  <Enter>
  14299.  
  14300.            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
  14301.    0000   00 00 00 00 AC FF 00 00 00 FF FF FF FF 00 FF FF
  14302.    0010   00 00 00 00 AC FF 00 00 00 FF FF FF FF 00 FF FF
  14303.           .
  14304.           .
  14305.           .
  14306.    00E0   FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
  14307.    00F0   FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
  14308.  
  14309.    [C:\]
  14310.    ──────────────────────────────────────────────────────────────────────────
  14311.  
  14312.    Figure 14-7.  Sample output of PORTS.EXE.
  14313.  
  14314.  
  14315.  
  14316.  ────────────────────────────────────────────────────────────────────────────
  14317.  Chapter 15  Time, Date, and Timers
  14318.  
  14319.    Because OS/2 is a preemptive multitasking system, it is both time-driven
  14320.    and time-sensitive. Its time-related responsibilities include:
  14321.  
  14322.    ■  Dividing the available CPU cycles (time-slicing) among the active
  14323.       threads according to their priority
  14324.  
  14325.    ■  Detecting I/O timeouts
  14326.  
  14327.    ■  Providing user-programmable timer events
  14328.  
  14329.    ■  Maintaining the current date and current time of day
  14330.  
  14331.    ■  Stamping the directory entries for files with the date and time they
  14332.       were created or last modified
  14333.  
  14334.    On PC/AT and PS/2 compatible machines, OS/2 relies on two hardware
  14335.    components to carry out these duties: a battery-powered clock/calendar
  14336.    chip and a programmable counter/timer chip.
  14337.  
  14338.    The battery-powered clock/calendar chip runs autonomously, separate from
  14339.    the rest of the computer system. Whenever the system is turned on or
  14340.    restarted, OS/2 initializes its internal time and date by reading the I/O
  14341.    ports that provide access to this chip. You can update the clock/calendar
  14342.    chip with the TIME and DATE commands or with the SETUP program.
  14343.  
  14344.    The clock/calendar chip is also programmed during system initialization to
  14345.    generate a hardware interrupt (called the "timer tick") at intervals of 31
  14346.    milliseconds (approximately 32 Hz). Each time a timer tick occurs, the
  14347.    currently executing thread is interrupted, and control is transferred to
  14348.    the clock driver's interrupt handler. The handler updates the system's
  14349.    internal time, date, and millisecond counters and then notifies the other
  14350.    kernel and driver modules of the timer tick by means of a special kernel
  14351.    dispatcher. Finally, the clock driver transfers control to the scheduler,
  14352.    which examines its list of eligible threads and selects one for execution.
  14353.  
  14354.    The programmable counter/timer chip (an Intel 8253 or equivalent) is set
  14355.    up during system initialization to provide interrupts at 55-millisecond
  14356.    intervals (18.2 Hz). This interrupt is masked off when any protected mode
  14357.    session is in the foreground. When the real mode session is in the
  14358.    foreground, the counter/timer chip's interrupt is enabled, serviced on Int
  14359.    08H (IRQ0), and distributed to real mode applications via Int 1CH for
  14360.    compatibility with MS-DOS.
  14361.  
  14362.    In addition to maintaining the date and the time and servicing periodic
  14363.    timer interrupts for its own purposes, OS/2 exports API functions that
  14364.    allow application programs to do the following:
  14365.  
  14366.    ■  Obtain or set the system time and date
  14367.  
  14368.    ■  Suspend execution for a predetermined interval
  14369.  
  14370.    ■  Be notified by means of a semaphore when a specified interval elapses
  14371.  
  14372.    These API functions are summarized in Figure 15-1. The functions to
  14373.    suspend execution or to report elapsed time with semaphores accept their
  14374.    time parameters in milliseconds (msec.). These values are always rounded
  14375.    up to a multiple of the timer tick interval and are accurate only to
  14376.    within one or two timer ticks. The length of a timer tick interval is
  14377.    available in the global information segment (see p. 524). Because this
  14378.    value is OEM-dependent, programs should obtain it at run time rather than
  14379.    having it hard-coded.
  14380.  
  14381. ╓┌─┌─────────────────────────┌───────────────────────────────────────────────╖
  14382.    Function                  Description
  14383.    ──────────────────────────────────────────────────────────────────────────
  14384.    DosGetInfoSeg            Obtains selectors for global and local
  14385.                              information segments; global information segment
  14386.                              includes current date, time, time zone, and day
  14387.    Function                  Description
  14388.    ──────────────────────────────────────────────────────────────────────────
  14389.                             includes current date, time, time zone, and day
  14390.                              of the week
  14391.  
  14392.    DosGetDateTime           Returns current date, time, time zone, and day
  14393.                              of the week
  14394.  
  14395.    DosSetDateTime           Sets system date and time
  14396.  
  14397.    DosSleep                 Suspends execution of a thread for a programmed
  14398.                              interval
  14399.  
  14400.    DosTimerAsync            Starts one-shot asynchronous timer that clears
  14401.                              system semaphore
  14402.  
  14403.    DosTimerStart            Starts periodic asynchronous timer that clears
  14404.                              system semaphore
  14405.  
  14406.    DosTimerStop             Disables timer previously activated with
  14407.                              DosTimerAsync or DosTimerStart
  14408.    Function                  Description
  14409.    ──────────────────────────────────────────────────────────────────────────
  14410.                             DosTimerAsync or DosTimerStart
  14411.    ──────────────────────────────────────────────────────────────────────────
  14412.  
  14413.  
  14414.    Figure 15-1.  The OS/2 API function calls for time, date, and programmable
  14415.    timers.
  14416.  
  14417.  
  14418.  Reading and Setting Date and Time
  14419.  
  14420.    Your application program can determine the current system time and date in
  14421.    two ways. The method you use depends on the characteristics of your
  14422.    program.
  14423.  
  14424.    For the average applications program, the preferred method is to use
  14425.    DosGetInfoSeg to get a selector for the global information segment.
  14426.    Information related to the current date and time is located at specific
  14427.    offsets within the segment, as shown in Figure 15-2. An actual memory
  14428.    dump of the global information segment is in Figure 15-3.
  14429.  
  14430.    Offset      Length       Contents
  14431.    ──────────────────────────────────────────────────────────────────────────
  14432.    00H         4            Elapsed time from 1-1-1970 in seconds
  14433.    04H         4            Milliseconds since system boot
  14434.    08H         1            Hours (0─23)
  14435.    09H         1            Minutes (0─59)
  14436.    0AH         1            Seconds (0─59)
  14437.    0BH         1            Hundredths of a second (0─99)
  14438.    0CH         2            Time zone (minutes ± GMT) (-1 = undefined)
  14439.    0EH         2            Timer interval (units = 0.0001 seconds)
  14440.    10H         1            Day (1─31)
  14441.    11H         1            Month (1─12)
  14442.    12H         2            Year (1980+)
  14443.    14H         1            Day of the week (0 = Sunday, 1 = Monday, etc.)
  14444.    ──────────────────────────────────────────────────────────────────────────
  14445.  
  14446.    Figure 15-2.  Format of current date and time in global information
  14447.    segment.
  14448.  
  14449.               Month  Weekday     Hours  Minutes
  14450.                   │        │         │  │
  14451.               0  1│ 2  3  4│ 5  6  7 │8 │9  A  B  C  D  E  F  0123456789ABCDEF
  14452.    0060:0000 B9 36│57 20 35│8D 0E 00 16 1D 0D 4E FF FF 36 01  .6W 5......N..6.
  14453.    0060:0010 0C 03 C3 07 04 05 00 00 04 10 10│00 13 00 01 03  ................
  14454.    0060:0020 1F│00 F8│00 01 00 00 00 00 00 00│00 00 00 00 00  ................
  14455.    0060:0030 00│00 00│00 00 00 00 00 00 00 00│00 00 00 00 00  ................
  14456.    0060:0040 00│00 00│00 00 00               │                ......
  14457.                │     │                       │
  14458.                │     │                       │
  14459.              Day  Year                 Seconds
  14460.  
  14461.    Figure 15-3.  A hex and ASCII dump of a global information segment.
  14462.  
  14463.    When the selector for the global information segment is in hand, your
  14464.    program can inspect the date or time as often as necessary without the
  14465.    overhead of additional calls to OS/2 (Figure 15-4 on the following page).
  14466.    Be careful, however, not to be misled by the occurrence of a rollover in
  14467.    one of the fields while your program is preempted by an interrupt handler
  14468.    or another thread. To protect against this, read the fields of interest
  14469.    repeatedly until you get two sets that are the same. An alternative (but
  14470.    strongly discouraged) technique is to use an IOPL routine that disables
  14471.    interrupts, reads the global information segment values, and then
  14472.    reenables interrupts.
  14473.  
  14474.    ──────────────────────────────────────────────────────────────────────────
  14475.                                    ; fields in global info segment...
  14476.    hour    equ     08h             ; hours
  14477.    min     equ     09h             ; minutes
  14478.    sec     equ     0ah             ; seconds
  14479.    cseg    equ     0bh             ; hundredths of a second
  14480.  
  14481.  
  14482.    gseg    dw      ?               ; receives selector for
  14483.                                    ; global information segment
  14484.  
  14485.    lseg    dw      ?               ; receives selector for
  14486.                                    ; local information segment
  14487.  
  14488.            .
  14489.            .
  14490.            .
  14491.                                    ; get info seg selectors...
  14492.            push    ds              ; receives global selector
  14493.            push    offset DGROUP:gseg
  14494.            push    ds              ; receives local selector
  14495.            push    offset DGROUP:lseg
  14496.            call    DosGetInfoSeg   ; transfer to OS/2
  14497.            or      ax,ax           ; were selectors attained?
  14498.            jnz     error           ; jump if function failed
  14499.  
  14500.                                    ; else get current time...
  14501.            mov     es,gseg         ; load selector for global
  14502.                                    ; information segment
  14503.  
  14504.                                    ; let AL = hours, AH = minutes
  14505.    label1: mov     ax,word ptr es:hour
  14506.                                    ; let BL = seconds, BH = centiseconds
  14507.            mov     bx,word ptr es:sec
  14508.                                    ; read time again...
  14509.            mov     cx,word ptr es:hour
  14510.            mov     dx,word ptr es:sec
  14511.  
  14512.            cmp     ax,cx           ; check for rollover...
  14513.            jne     label1          ; rollover occurred, start over
  14514.            cmp     bx,dx
  14515.            jne     label1          ; rollover occurred, start over
  14516.            .
  14517.            .
  14518.            .
  14519.    ──────────────────────────────────────────────────────────────────────────
  14520.  
  14521.    Figure 15-4.  Obtaining the selector for the global information segment
  14522.    with DosGetInfoSeg and then using that selector to obtain the current
  14523.    time.
  14524.  
  14525.    The second method for getting the date and time is ordinarily used only by
  14526.    programs that need to obtain these values infrequently or that need to
  14527.    alter the date or time. It involves use of the function DosGetDateTime,
  14528.    which is called with the address of an 11-byte structure. OS/2 fills in
  14529.    the structure with the current date, time, time zone, and day of the week
  14530.    in the format shown in Figure 15-5.
  14531.  
  14532.    Because this method requires an operating system call each time the date
  14533.    or time is inspected, it is considerably slower than using the global
  14534.    information segment. It is useful mainly because the organization of the
  14535.    structure is exactly the same as for DosSetDateTime. When a program needs
  14536.    to modify some part of the time or date, it can first call DosGetDateTime
  14537.    to fill the structure with valid information, make any necessary changes,
  14538.    and then write the updated information back to the operating system with
  14539.    DosSetDateTime (Figure 15-6 on the following page).
  14540.  
  14541.    Offset      Length       Contents
  14542.    ──────────────────────────────────────────────────────────────────────────
  14543.    00H         1            Hours (0─23)
  14544.    01H         1            Minutes (0─59)
  14545.    02H         1            Seconds (0─59)
  14546.    03H         1            Hundredths of a second (0─99)
  14547.    04H         1            Day (1─31)
  14548.    05H         1            Month (1─12)
  14549.    06H         2            Year (1980+)
  14550.    08H         2            Time zone (minutes ± GMT) (-1 = undefined)
  14551.    0AH         1            Day of week (0 = Sunday, 1 = Monday, etc.)
  14552.    ──────────────────────────────────────────────────────────────────────────
  14553.  
  14554.    Figure 15-5.  Format of an 11-byte structure used by both DosGetDateTime
  14555.    and DosSetDateTime to hold date and time information.
  14556.  
  14557.    ──────────────────────────────────────────────────────────────────────────
  14558.    dtinfo  label   byte            ; date/time information
  14559.    hour    db      0               ; 0
  14560.    min     db      0               ; 1
  14561.    sec     db      0               ; 2
  14562.    csec    db      0               ; 3
  14563.    day     db      0               ; 4
  14564.    mon     db      0               ; 5
  14565.    year    dw      0               ; 6
  14566.    zone    dw      0               ; 8 minutes +/- GMT
  14567.    dow     db      0               ; 10 day of week
  14568.  
  14569.            .
  14570.            .
  14571.            .
  14572.                                    ; first read date/time...
  14573.            push    ds              ; receives date/time info
  14574.            push    offset DGROUP:dtinfo
  14575.            call    DosGetDateTime  ; transfer to OS/2
  14576.            or      ax,ax           ; did read succeed?
  14577.            jnz     error           ; jump if function failed
  14578.  
  14579.            mov     hour,12         ; set time to 12:00
  14580.            mov     min,0           ; clear minutes
  14581.            mov     sec,0           ; and seconds
  14582.            mov     csec,0          ; and hundredths of a second
  14583.  
  14584.                                    ; now set date/time...
  14585.            push    ds              ; address of structure
  14586.            push    offset DGROUP:dtinfo
  14587.            call    DosSetDateTime  ; transfer to OS/2
  14588.            or      ax,ax           ; was time/date set?
  14589.            jnz     error           ; jump if function failed
  14590.            .
  14591.            .
  14592.            .
  14593.    ──────────────────────────────────────────────────────────────────────────
  14594.  
  14595.    Figure 15-6.  Example of altering the system time with DosSetDateTime,
  14596.    using DosGetDateTime first to obtain all the other current information. In
  14597.    this example the system time is set to 12:00:00:00 (noon).
  14598.  
  14599.  
  14600.  Programmed Delays
  14601.  
  14602.    The function DosSleep allows a thread to suspend itself for a programmed
  14603.    interval. Using DosSleep, a thread can make itself dormant so that it
  14604.    doesn't tie up CPU resources, yet wake up periodically and check for an
  14605.    event, poll a device, or find out if a previously unavailable resource (a
  14606.    file, for example, that was locked by another process) has been freed up
  14607.    so that the thread can continue. Only the thread that calls DosSleep is
  14608.    suspended; other threads in the same process are not affected.
  14609.  
  14610.    DosSleep is one of the simplest functions to use in the entire OS/2 API.
  14611.    Its only argument is an unsigned double precision number of milliseconds,
  14612.    allowing an upper limit on the delay time of about 4.29 * 10^6 seconds
  14613.    (1193 hours). Under normal circumstances this function cannot return an
  14614.    error. Figure 15-7 illustrates the use of DosSleep by a thread that
  14615.    displays the system time at approximately 1-second intervals.
  14616.  
  14617.    A program should not rely on the DosSleep function if it needs to resume
  14618.    execution at an exact time. The delay produced by a DosSleep call is
  14619.    accurate only to the granularity of the timer tick interval; its duration
  14620.    will always be at least the requested interval, rounded up to the next
  14621.    clock tick. In addition, the requested interval determines only the time
  14622.    at which the thread is again marked runnable; it does not guarantee that
  14623.    the thread will run at that time. The revived execution of the thread can
  14624.    be delayed unpredictably by other events in the system, such as servicing
  14625.    of hardware interrupts or the activity of other, higher-priority threads.
  14626.    In general, using DosSleep to obtain clocklike behavior is inevitably
  14627.    unsatisfactory because of the cumulative effect of delays and rounding
  14628.    errors.
  14629.  
  14630.    An interesting special use of DosSleep is to yield processor time
  14631.    explicitly. If the DosSleep parameter is zero, the thread gives up the
  14632.    remainder of its current timeslice but is rescheduled normally for its
  14633.    next timeslice.
  14634.  
  14635.    ──────────────────────────────────────────────────────────────────────────
  14636.            .
  14637.            .
  14638.            .
  14639.    showit: call    display         ; update clock display
  14640.  
  14641.                                    ; suspend for 1 second...
  14642.            push    0               ; push double precision
  14643.            push    1000            ; value of 1000 msec.
  14644.            call    DosSleep        ; transfer to OS/2
  14645.            or      ax,ax           ; unexpected error?
  14646.            jnz     error           ; jump if function failed
  14647.  
  14648.            jmp     showit          ; now display time again...
  14649.            .
  14650.            .
  14651.            .
  14652.    ──────────────────────────────────────────────────────────────────────────
  14653.  
  14654.    Figure 15-7.  A simple clock utility, which uses the DosSleep function to
  14655.    suspend itself for 1 second and then displays the current time. Because
  14656.    DosSleep is subject to rounding errors, and because reactivation of the
  14657.    thread can be delayed by other activity in the system, the display does
  14658.    not occur at precise 1-second intervals.
  14659.  
  14660.  
  14661.  Using Timers
  14662.  
  14663.    Two OS/2 functions, DosTimerAsync and DosTimerStart, provide for an
  14664.    asynchronous signal to a process after a programmed delay. The timers
  14665.    communicate with the process by clearing a system semaphore. DosTimerAsync
  14666.    sets up a "one-shot" timer; DosTimerStart establishes a "periodic" timer
  14667.    that continues to clear the semaphore at the requested interval until the
  14668.    timer is disabled.
  14669.  
  14670.    A process that uses timers must first create a system semaphore and
  14671.    initialize it to a "set" state (see Chapter 13). The process then calls
  14672.    DosTimerAsync or DosTimerStart to create and start the timer. Both
  14673.    functions require the same three parameters: a system semaphore handle, an
  14674.    unsigned double precision timer interval, and the address of a variable.
  14675.    If the timer is successfully created, the system returns a timer handle to
  14676.    the variable; this handle can be used later with DosTimerStop to disable
  14677.    the timer.
  14678.  
  14679.    Once the timer is armed, the process can go on to other work. A thread
  14680.    within the process can synchronize with the timer by calling DosMuxSemWait
  14681.    (preferred) or DosSemWait with a timeout value of -1 to block on the
  14682.    semaphore. Alternatively, it can test for the expiration of the timer at
  14683.    suitable intervals by calling DosSemWait with a timeout value of 0──
  14684.    causing DosSemWait to return immediately with an error code if the
  14685.    semaphore is set. To synchronize with a periodic timer, the process must
  14686.    reset the semaphore immediately after the timer clears it so that it will
  14687.    not miss subsequent firings of the timer.
  14688.  
  14689.    It is, of course, quite practical to control multiple threads with the
  14690.    same timer──all threads in a process have equal access to any system
  14691.    semaphores created or opened by that process. When a process is using a
  14692.    periodic timer, one thread should block on the semaphore with DosSemWait
  14693.    and be responsible for resetting the semaphore; by promoting this thread
  14694.    to a "time-critical" priority, you can ensure that its execution will
  14695.    never be suppressed by other activity in the system. The other threads
  14696.    should run at normal priority and block on the semaphore with
  14697.    DosMuxSemWait (which is the only semaphore function that is edge-triggered
  14698.    rather than level-triggered).
  14699.  
  14700.    Figure 15-8 demonstrates the use of a periodic timer to display the
  14701.    system time at 1-second intervals. This approach is superior to the use of
  14702.    DosSleep shown in Figure 15-7 because the overhead of the time display
  14703.    procedure is "hidden" inside the interval controlled by the timer.
  14704.    However, the timer is still subject to rounding errors and to delays
  14705.    caused by hardware interrupt processing or the activity of other,
  14706.    higher-priority threads.
  14707.  
  14708.    ──────────────────────────────────────────────────────────────────────────
  14709.    sname   db      '\SEM\TIMER.SEM',0
  14710.  
  14711.    shandle dd      0               ; semaphore handle
  14712.  
  14713.    thandle dw      0               ; timer handle
  14714.  
  14715.            .
  14716.            .
  14717.            .
  14718.                                    ; create system semaphore...
  14719.            push    1               ; 1 = not exclusive ownership
  14720.            push    ds              ; receives semaphore handle
  14721.            push    offset DGROUP:shandle
  14722.            push    ds              ; address of semaphore name
  14723.            push    offset DGROUP:sname
  14724.            call    DosCreateSem    ; transfer to OS/2
  14725.            or      ax,ax           ; semaphore created?
  14726.            jnz     error           ; jump if function failed...
  14727.  
  14728.                                    ; create periodic timer...
  14729.            push    0               ; interval = 1000 msec.
  14730.            push    1000
  14731.            push    word ptr shandle+2 ; semaphore handle
  14732.            push    word ptr shandle
  14733.            push    ds              ; receives timer handle
  14734.            push    offset DGROUP:thandle
  14735.            call    DosTimerStart   ; transfer to OS/2
  14736.            or      ax,ax           ; timer created?
  14737.            jnz     error           ; jump if function failed
  14738.  
  14739.    showit:                         ; set the semaphore...
  14740.            push    word ptr shandle+2 ; semaphore handle
  14741.            push    word ptr shandle
  14742.            call    DosSemSet       ; transfer to OS/2
  14743.            or      ax,ax           ; did set succeed?
  14744.            jnz     error           ; jump if function failed
  14745.  
  14746.            call    display         ; update clock display
  14747.  
  14748.                                    ; now block on semaphore...
  14749.            push    word ptr shandle+2 ; semaphore handle
  14750.            push    word ptr shandle
  14751.            push    -1              ; timeout = -1
  14752.            push    -1              ; (wait indefinitely)
  14753.            call    DosSemWait      ; transfer to OS/2
  14754.            or      ax,ax           ; unexpected error?
  14755.            jnz     error           ; jump if wait failed
  14756.  
  14757.            jmp     showit          ; now display time again...
  14758.            .
  14759.            .
  14760.            .
  14761.    ──────────────────────────────────────────────────────────────────────────
  14762.  
  14763.    Figure 15-8.  Another simple clock timer, similar to that in Figure 15-7,
  14764.    but which uses a periodic timer and system semaphore (rather than
  14765.    DosSleep) to trigger display of the clock.
  14766.  
  14767.  
  14768.  The Clock Device
  14769.  
  14770.    The system's device driver for the real time clock masquerades as a
  14771.    character device driver and has the logical name CLOCK$. The kernel
  14772.    identifies the clock driver by a special bit in the attribute word of its
  14773.    header, so the device name has no special significance to the system
  14774.    itself.
  14775.  
  14776.    If you use DosOpen to obtain a handle for the CLOCK$ device, you can use
  14777.    ordinary DosRead and DosWrite calls to read from it and write data to it.
  14778.    This data must be in a special 6-byte format:
  14779.  
  14780.    Offset      Length       Contents
  14781.    ──────────────────────────────────────────────────────────────────────────
  14782.    00H         2            Days since January 1, 1980
  14783.    02H         1            Minutes (0─59)
  14784.    03H         1            Hours (0─23)
  14785.    04H         1            Hundredths of a second (0─99)
  14786.    05H         1            Seconds (0─59)
  14787.    ──────────────────────────────────────────────────────────────────────────
  14788.  
  14789.    This is the same data format used by the real time clock driver in MS-DOS
  14790.    and PC-DOS systems. There is no particular reason to use this CLOCK$
  14791.    driver capability instead of DosGetDateTime or inspection of the global
  14792.    information segment; it is mentioned here only for completeness.
  14793.  
  14794.  
  14795.  
  14796.  ────────────────────────────────────────────────────────────────────────────
  14797.  SECTION 5  CUSTOMIZING OS/2
  14798.  ────────────────────────────────────────────────────────────────────────────
  14799.  
  14800.  
  14801.  
  14802.  ────────────────────────────────────────────────────────────────────────────
  14803.  Chapter 16  Filters
  14804.  
  14805.    A filter is, essentially, a program that operates on a stream of
  14806.    characters. The source and the destination of the character stream can be
  14807.    files, another program (by means of a pipe), or almost any character
  14808.    device. The transformation applied by the filter to the character stream
  14809.    can be as simple as character substitution or as complex as fitting a
  14810.    curve to a set of coordinates.
  14811.  
  14812.    The standard OS/2 package includes three simple filters: SORT, which
  14813.    alphabetically sorts text line by line; FIND, which searches a text stream
  14814.    to match a specified string; and MORE, which displays text one screenful
  14815.    at a time.
  14816.  
  14817.  
  14818.  System Support for Filters
  14819.  
  14820.    The operation of a filter program relies on two OS/2 features that are
  14821.    modeled after UNIX/XENIX: standard devices and redirectable I/O.
  14822.  
  14823.    The standard devices are represented by three handles that are established
  14824.    by the command interpreter (CMD.EXE) and inherited by each newly created
  14825.    process from its immediate parent. Thus, the standard device handles are
  14826.    already opened when a process begins its execution, and the process can
  14827.    use them for read and write operations without further preliminaries. The
  14828.    default assignments of the standard device handles are as follows:
  14829.  
  14830.    Handle   Name                       Default Device
  14831.    ──────────────────────────────────────────────────────────────────────────
  14832.    0        stdin (standard input)     KBD$
  14833.    1        stdout (standard output)   SCREEN$
  14834.    2        stderr (standard error)    SCREEN$
  14835.    ──────────────────────────────────────────────────────────────────────────
  14836.  
  14837.    When the user executes a program by entering its name at the system
  14838.    (CMD.EXE) prompt or in a batch file, any or all of the three standard
  14839.    device handles can be redirected from its default device to another file,
  14840.    to a character device, or to a process. This redirection is accomplished
  14841.    by including one of the six special directives <, >, >>, 2>, 2>>, or | on
  14842.    the command line, as illustrated in Figure 16-1.
  14843.  
  14844.    The following command, for example, causes the SORT filter to read its
  14845.    input from the file MYFILE.TXT, sort the lines alphabetically, and write
  14846.    resulting text to the character device PRN (the logical name for the
  14847.    system's list device):
  14848.  
  14849.    [C:\] SORT <MYFILE.TXT >PRN  <Enter>
  14850.  
  14851.    The actions requested by the redirection parameters take place at the
  14852.    level of CMD.EXE and are invisible to the programs they affect. As shown
  14853.    at the end of this chapter, any other process can also institute such
  14854.    redirection.
  14855.  
  14856.    A program that uses Kbd or Vio calls for higher performance is unaffected
  14857.    by redirection directives on the command line. The same is true of a
  14858.    program that calls DosOpen for KBD$, SCREEN$, or CON and uses the
  14859.    resulting handle for I/O. This independence can frustrate a user who is
  14860.    trying to drive a program with a script or capture its output into a
  14861.    file──the program doesn't behave "as expected." A well-behaved program
  14862.    should call DosQHandType to determine if one or more of its standard
  14863.    devices have been redirected, then use DosRead and DosWrite with the
  14864.    standard handles (rather than the Kbd and Vio subsystems) if redirection
  14865.    is in effect.
  14866.  
  14867. ╓┌─┌─────────────────────────┌───────────────────────────────────────────────╖
  14868.    Syntax                    Resulting Redirection
  14869.    ──────────────────────────────────────────────────────────────────────────
  14870.    Syntax                    Resulting Redirection
  14871.    ──────────────────────────────────────────────────────────────────────────
  14872.    < file                    The program takes its standard input from the
  14873.                              specified file or device instead of from the
  14874.                              keyboard.
  14875.  
  14876.    > file                    The program sends its standard output to the
  14877.                              specified file or device instead of to the
  14878.                              display (also 1> file).
  14879.  
  14880.    >> file                   The program appends its standard output to the
  14881.                              current contents of the specified file instead
  14882.                              of sending it to the display (also 1>> file).
  14883.  
  14884.    2> file                   The program sends its standard error output to
  14885.                              the specified file or device instead of to the
  14886.                              display.
  14887.  
  14888.    2>> file                  The program appends its standard error output to
  14889.                              the current contents of the specified file
  14890.                              instead of sending it to the display.
  14891.    Syntax                    Resulting Redirection
  14892.    ──────────────────────────────────────────────────────────────────────────
  14893.                             instead of sending it to the display.
  14894.  
  14895.    p1 | p2                   The standard output of process p1 is routed to
  14896.                              become the standard input of process p2. (The
  14897.                              output of p1 is said to be piped.)
  14898.    ──────────────────────────────────────────────────────────────────────────
  14899.  
  14900.  
  14901.    Figure 16-1.  Command syntax for redirecting standard device handles.
  14902.  
  14903.  
  14904.  How Filters Work
  14905.  
  14906.    By convention, a filter program reads its text from the standard input
  14907.    device and writes the results of its operations to the standard output
  14908.    device. When the end of the input stream is reached, the filter simply
  14909.    terminates. As a result, filters are both flexible and simple.
  14910.  
  14911.    The flexibility of filter programs derives from their indifference to the
  14912.    source of the data they process or the destination of their output. Any
  14913.    character device that has a logical name within the system (CON, COM1,
  14914.    COM2, PRN, LPT1, LPT2, LPT3, and so on), any file on any block device
  14915.    (local or network) known to the system, or any other program can supply
  14916.    input to a filter or accept its output.
  14917.  
  14918.    Although flexible, filters remain simple because they rely on their parent
  14919.    processes to supply standard input and standard output handles that have
  14920.    already been appropriately redirected. The parent is responsible for
  14921.    opening or creating any necessary files, checking the validity of logical
  14922.    character device names, and loading and executing the preceding or
  14923.    following process in a pipe. The filter can concern itself strictly with
  14924.    operating on the data; it can leave the I/O details to the operating
  14925.    system and to its parent.
  14926.  
  14927.  
  14928.  Building a Filter
  14929.  
  14930.    Creating a new filter for OS/2 is straightforward. In its simplest form, a
  14931.    filter need use only DosRead and DosWrite to get characters from standard
  14932.    input and to send them to standard output; it operates on the text stream
  14933.    on a character-by-character or line-by-line basis.
  14934.  
  14935.    Figures 16-2 and 16-3 contain template filters in both assembly language
  14936.    (PROTO.ASM) and C (PROTO.C). The C and assembler versions run at roughly
  14937.    the same speed, although the C version is several times larger. Both can
  14938.    be assembled or compiled, linked, and run exactly as shown. Of course, in
  14939.    this form they simply function like a slow COPY command.
  14940.  
  14941.    ──────────────────────────────────────────────────────────────────────────
  14942.            title     PROTO.ASM -- Filter Template
  14943.            page      55,132
  14944.            .286
  14945.    ;
  14946.    ; PROTO.ASM
  14947.    ;
  14948.    ; MASM filter template for OS/2.
  14949.    ;
  14950.    ; Assemble with:  C> masm proto.asm;
  14951.    ; Link with:  C> link proto,,,os2;
  14952.    ;
  14953.    ; Usage is:  C> proto <source >destination
  14954.    ;
  14955.    ; Copyright (C) 1988 Ray Duncan
  14956.    ;
  14957.  
  14958.    stdin   equ     0               ; standard input device
  14959.    stdout  equ     1               ; standard output device
  14960.    stderr  equ     2               ; standard error device
  14961.  
  14962.    cr      equ     0dh             ; ASCII carriage return
  14963.    lf      equ     0ah             ; ASCII linefeed
  14964.  
  14965.    bufsize equ     256             ; I/O buffer size
  14966.  
  14967.            extrn   DosRead:far
  14968.            extrn   DosWrite:far
  14969.            extrn   DosExit:far
  14970.  
  14971.  
  14972.    DGROUP  group   _DATA           ; 'automatic data group'
  14973.  
  14974.    _DATA   segment word public 'DATA'
  14975.  
  14976.    input   db      bufsize dup (?) ; storage for input line
  14977.    output  db      bufsize dup (?) ; storage for output line
  14978.  
  14979.    rlen    dw      ?               ; receives bytes read
  14980.    wlen    dw      ?               ; receives bytes written
  14981.  
  14982.    _DATA   ends
  14983.  
  14984.  
  14985.    _TEXT   segment word public 'CODE'
  14986.  
  14987.            assume  cs:_TEXT,ds:DGROUP
  14988.  
  14989.    main    proc    far             ; entry point from OS/2
  14990.  
  14991.            push    ds              ; make DGROUP addressable
  14992.            pop     es              ; via ES register too
  14993.            assume  es:DGROUP
  14994.            cld                     ; safety first
  14995.  
  14996.    main1:                          ; read line from standard input...
  14997.            push    stdin           ; standard input handle
  14998.            push    ds              ; buffer address
  14999.            push    offset DGROUP:input
  15000.            push    bufsize         ; buffer length
  15001.            push    ds              ; receives bytes read
  15002.            push    offset DGROUP:rlen
  15003.            call    DosRead         ; transfer to OS/2
  15004.            or      ax,ax           ; was read successful?
  15005.            jnz     main3           ; exit if any error
  15006.  
  15007.            mov     ax,rlen         ; get length of input
  15008.            or      ax,ax           ; any characters read?
  15009.            jz      main2           ; jump if end of stream
  15010.  
  15011.            call    translate       ; translate line if necessary
  15012.  
  15013.            or      ax,ax           ; anything to output?
  15014.            jz      main1           ; no, go read another line
  15015.  
  15016.                                    ; write line to standard output...
  15017.            push    stdout          ; standard output handle
  15018.            push    ds              ; buffer address
  15019.            push    offset DGROUP:output
  15020.            push    ax              ; buffer length
  15021.            push    ds              ; receives bytes written
  15022.            push    offset DGROUP:wlen
  15023.            call    DosWrite        ; transfer to OS/2
  15024.            or      ax,ax           ; write successful?
  15025.            jnz     main3           ; exit if any error
  15026.  
  15027.            jmp     main1           ; go read another line
  15028.  
  15029.    main2:                          ; end of stream reached
  15030.            push    1               ; 1 = end all threads
  15031.            push    0               ; 0 = exit code
  15032.            call    DosExit         ; final exit to OS/2
  15033.  
  15034.    main3:                          ; error encountered
  15035.            push    1               ; 1 = end all threads
  15036.            push    1               ; 1 = exit code
  15037.            call    DosExit         ; final exit to OS/2
  15038.  
  15039.    main    endp                    ; end of main procedure
  15040.    ; Perform any necessary translation on line stored in
  15041.    ; 'input' buffer, leaving result in 'output' buffer.
  15042.    ;
  15043.    ; Call with:    AX = length of data in 'input' buffer
  15044.    ;
  15045.    ; Return:       AX = length to write to standard output
  15046.    ;
  15047.    ; Action of template routine is simply to copy the line.
  15048.  
  15049.    translate proc  near
  15050.  
  15051.                                    ; copy input to output...
  15052.            mov     si,offset DGROUP:input
  15053.            mov     di,offset DGROUP:output
  15054.            mov     cx,ax
  15055.            rep movsb
  15056.            ret                     ; return AX = length unchanged
  15057.  
  15058.    translate endp
  15059.  
  15060.  
  15061.    _TEXT   ends
  15062.  
  15063.            end     main            ; program entry point
  15064.    ──────────────────────────────────────────────────────────────────────────
  15065.  
  15066.    Figure 16-2.  Assembly language source code for PROTO.ASM, a template for
  15067.    a filter.
  15068.  
  15069.    ──────────────────────────────────────────────────────────────────────────
  15070.    /*
  15071.            PROTO.C
  15072.  
  15073.            A filter template for OS/2.
  15074.  
  15075.            Compile with:  C> cl proto.c
  15076.  
  15077.            Usage is:  C> proto <source >destination
  15078.  
  15079.            Copyright (C) 1988 Ray Duncan
  15080.    */
  15081.  
  15082.    #include <stdio.h>
  15083.  
  15084.    #define STDIN   0                   /* standard input handle */
  15085.    #define STDOUT  1                   /* standard output handle */
  15086.    #define BUFSIZE 256                 /* I/O buffer size */
  15087.    #define API unsigned extern far pascal
  15088.    API DosRead(unsigned, void far *, unsigned, unsigned far *);
  15089.    API DosWrite(unsigned, void far *, unsigned, unsigned far *);
  15090.  
  15091.    static char input[BUFSIZE];         /* buffer for input line */
  15092.    static char output[BUFSIZE];        /* buffer for output line */
  15093.  
  15094.    main(int argc,char *argv[])
  15095.    {
  15096.        int rlen, wlen;                 /* scratch variables */
  15097.  
  15098.        while(1)                        /* do until end of file */
  15099.        {
  15100.                                        /* get line from standard
  15101.                                           input stream */
  15102.            if(DosRead(STDIN, input, BUFSIZE, &rlen))
  15103.                exit(1);                /* exit if read error */
  15104.  
  15105.            if(rlen == 0) exit(0);      /* exit if end of stream */
  15106.  
  15107.                                        /* write translated line to
  15108.                                           standard output stream */
  15109.            if(DosWrite(STDOUT, output, translate(rlen), &wlen))
  15110.                exit(1);                /* exit if write error */
  15111.        }
  15112.    }
  15113.  
  15114.    /*
  15115.        Perform any necessary translation on input line,
  15116.        leaving the resulting text in output buffer.
  15117.        Returns length of translated line (may be zero).
  15118.    */
  15119.  
  15120.    int translate(int length)
  15121.    {
  15122.        memcpy(output,input,length);    /* template action is */
  15123.                                        /* to copy input line */
  15124.        return(length);                 /* and return its length */
  15125.    }
  15126.    ──────────────────────────────────────────────────────────────────────────
  15127.  
  15128.    Figure 16-3.  C language source code for PROTO.C, a template for a filter.
  15129.  
  15130.    To obtain a filter that does something useful, you must insert between the
  15131.    reads and writes a routine that performs some modification of the text
  15132.    stream that is flowing by. For example, Figures 16-4 and 16-5 contain
  15133.    the source code for a new translate function that you can substitute for
  15134.    the original translate in PROTO.ASM and PROTO.C. After this alteration,
  15135.    the filter converts all uppercase input characters (A─Z) to lowercase
  15136.    (a─z) output, leaving other characters unchanged.
  15137.  
  15138.    ──────────────────────────────────────────────────────────────────────────
  15139.    ;
  15140.    ; Copy input buffer to output buffer, translating
  15141.    ; all uppercase characters to lowercase.
  15142.    ;
  15143.    ; Call with:    AX = length of data in 'input' buffer
  15144.    ;
  15145.    ; Return:       AX = length to write to standard output
  15146.    ;
  15147.  
  15148.    translate proc  near
  15149.  
  15150.            push    ax              ; save original length
  15151.  
  15152.                                    ; address of original line
  15153.            mov     si,offset DGROUP:input
  15154.  
  15155.                                    ; address for converted line
  15156.            mov     di,offset DGROUP:output
  15157.  
  15158.            mov     cx,ax           ; length to convert
  15159.            jcxz    trans3          ; exit if empty line
  15160.  
  15161.    trans1:                         ; convert character by character
  15162.  
  15163.            lodsb                   ; get input character
  15164.  
  15165.            cmp     al,'A'          ; if not A-Z, leave it alone
  15166.            jb      trans2
  15167.            cmp     al,'Z'
  15168.            ja      trans2
  15169.  
  15170.            add     al,'a'-'A'      ; it's A-Z, convert it
  15171.  
  15172.    trans2: stosb                   ; put into output buffer
  15173.  
  15174.            loop    trans1          ; loop until all characters
  15175.                                    ; have been transferred
  15176.  
  15177.    trans3: pop     ax              ; get back line length
  15178.            ret                     ; and return it in AX
  15179.  
  15180.    translate endp
  15181.    ──────────────────────────────────────────────────────────────────────────
  15182.  
  15183.    Figure 16-4.  A substitute assembly language translation routine to create
  15184.    a lowercasing filter.
  15185.  
  15186.    ──────────────────────────────────────────────────────────────────────────
  15187.    /*
  15188.        Translate uppercase characters to lowercase
  15189.        characters, leaving resulting text in output
  15190.        buffer and returning its length.
  15191.    */
  15192.  
  15193.    int translate(int length)
  15194.    {
  15195.        int i;                        /* scratch index */
  15196.  
  15197.        memcpy(output,input,length);  /* copy input to output */
  15198.  
  15199.        for(i = 0; i < length; i++)   /* lowercase the output */
  15200.        {
  15201.            if(output[i] >= 'A' && output[i] <= 'Z')
  15202.  
  15203.                output[i] += 'a'-'A';
  15204.        }
  15205.        return(length);               /* return its length */
  15206.    }
  15207.    ──────────────────────────────────────────────────────────────────────────
  15208.  
  15209.    Figure 16-5.  A substitute C language translation routine to create a
  15210.    lowercasing filter.
  15211.  
  15212.    As another example, Figure 16-6 contains the C source code for a
  15213.    line-oriented filter called FIND. This filter (which is similar to the
  15214.    FIND filter supplied with OS/2 but much simpler) is invoked with a command
  15215.    line in the following form:
  15216.  
  15217.      FIND "pattern" [<source] [>destination]
  15218.  
  15219.    FIND searches the input stream for lines containing the pattern specified
  15220.    on the command line. The line number and text of any line containing a
  15221.    match is sent to standard output, with any tabs expanded to 8-column
  15222.    stops.
  15223.  
  15224.    Unlike PROTO.C, which performed direct OS/2 function calls with DosRead
  15225.    and DosWrite, the FIND filter uses the C runtime library's gets() and
  15226.    printf() functions. This makes formatting of the output easier but
  15227.    drastically enlarges the size of the executable file.
  15228.  
  15229.    The FIND program in Figure 16-6 differs from the FIND filter in the OS/2
  15230.    retail package in several respects. First, it is not case-sensitive, so
  15231.    the pattern "foobar" will match "FOOBAR," "FooBar," and so forth. Second,
  15232.    this filter supports no switches; these are left as entertainment for the
  15233.    reader. Third, this program always reads from the standard input──it
  15234.    cannot open its own files.
  15235.  
  15236.    ──────────────────────────────────────────────────────────────────────────
  15237.    /*
  15238.            FIND.C
  15239.  
  15240.            Searches text stream for a string.
  15241.  
  15242.            Compile with:  C> cl find.c
  15243.  
  15244.            Usage is:  C> find "pattern" [<source] [>destination]
  15245.  
  15246.            Copyright (C) 1988 Ray Duncan
  15247.    */
  15248.  
  15249.    #include <stdio.h>
  15250.  
  15251.    #define TAB     '\x09'                  /* ASCII tab (^I) */
  15252.    #define BLANK   '\x20'                  /* ASCII space */
  15253.    #define TAB_WIDTH 8                     /* columns per tab stop */
  15254.    #define BUF_SIZE  256
  15255.  
  15256.    static char input[BUF_SIZE];            /* input line buffer */
  15257.    static char output[BUF_SIZE];           /* output line buffer */
  15258.    static char pattern[BUF_SIZE];          /* search pattern buffer */
  15259.  
  15260.    void writeline(int, char *);            /* function prototype */
  15261.  
  15262.    main(int argc, char *argv[])
  15263.    {
  15264.        int line = 0;                       /* initialize line variable */
  15265.  
  15266.        if(argc < 2)                        /* search pattern supplied? */
  15267.        {
  15268.            puts("find: missing pattern");  /* exit if no search pattern */
  15269.            exit(1);
  15270.        }
  15271.  
  15272.        strcpy(pattern,argv[1]);            /* save copy of search pattern */
  15273.        strupr(pattern);                    /* fold it to uppercase */
  15274.  
  15275.        while(gets(input) != NULL)          /* read a line from input */
  15276.        {
  15277.            line++;                         /* count lines */
  15278.  
  15279.            strcpy(output, input);          /* save copy of input string */
  15280.            strupr(input);                  /* fold input to uppercase */
  15281.            if(strstr(input, pattern))      /* if line contains pattern */
  15282.                writeline(line, output);    /* write it to standard output */
  15283.        }
  15284.        exit(0);                            /* terminate at end of file */
  15285.    }
  15286.  
  15287.    /*
  15288.        WRITELINE: Write line number and text to standard output,
  15289.        expanding any tab characters to stops defined by TAB_WIDTH.
  15290.    */
  15291.  
  15292.    void writeline(int line, char *p)
  15293.    {
  15294.        int i = 0;                         /* index to input line */
  15295.        int col = 0;                       /* output column counter */
  15296.  
  15297.        printf("\n%4d: ", line);           /* write line number */
  15298.  
  15299.        while(p[i] != NULL)                /* while not end of line */
  15300.        {
  15301.            if(p[i] == TAB)                /* if tab, expand it */
  15302.            {
  15303.                do putchar(BLANK);
  15304.                while((++col % TAB_WIDTH) != 0);
  15305.            }
  15306.            else                           /* otherwise leave it alone */
  15307.            {
  15308.                putchar(p[i]);             /* send character */
  15309.                col++;                     /* count columns */
  15310.            }
  15311.            i++;                           /* advance through input */
  15312.        }
  15313.    }
  15314.    ──────────────────────────────────────────────────────────────────────────
  15315.  
  15316.    Figure 16-6.  C source code for FIND.C, a FIND filter. This simple filter
  15317.    searches each line of a text stream for a pattern and sends the matching
  15318.    lines to the standard output.
  15319.  
  15320.  
  15321.  Using a Filter as a Child Process
  15322.  
  15323.    An application program can load and execute a filter as a child process to
  15324.    carry out a specific task instead of incorporating all the code necessary
  15325.    to do the same job itself. Before the child filter is loaded, the parent
  15326.    must arrange for the redirection of the standard input and the standard
  15327.    output handles (which the child inherits) to the files or to the character
  15328.    devices that will supply the filter's input and receive its output. This
  15329.    redirection is accomplished in the steps that follow.
  15330.  
  15331.    1.  The parent process uses DosDupHandle to create duplicates of its own
  15332.        standard input and standard output handles and saves the duplicates.
  15333.  
  15334.    2.  The parent uses DosOpen to open or create the files or devices that
  15335.        the child process will use for input and output.
  15336.  
  15337.    3.  The parent uses DosDupHandle to force its standard device handles to
  15338.        track the new file or device handles acquired in step 2.
  15339.  
  15340.    4.  The parent uses DosExecPgm with the synchronous option to load and
  15341.        execute the child process. The child inherits the redirected standard
  15342.        input and standard output handles and uses them to do its work. The
  15343.        parent regains control after the filter terminates.
  15344.  
  15345.    5.  The parent uses the duplicate handles created in step 1, together with
  15346.        DosDupHandle, to restore its own standard input and standard output
  15347.        handles to their original meanings.
  15348.  
  15349.    6.  The parent closes (with DosClose) the duplicate handles created in
  15350.        step 1 because they are no longer needed.
  15351.  
  15352.    It might seem as though the parent process could just as easily close its
  15353.    own standard input and standard output (handles 0 and 1), open the input
  15354.    and output files needed by the child, run the child process, close the
  15355.    files upon regaining control, and then reopen the CON device twice.
  15356.    Because the open operation always assigns the first free handle, this
  15357.    approach would have the desired effect as far as the child process is
  15358.    concerned. However, it would throw away any redirection that had been
  15359.    established for the parent process by its parent. Thus, the need to
  15360.    preserve any preexisting redirection of the parent's standard input and
  15361.    standard output, along with the desire to preserve the parent's usual
  15362.    output channel for messages right up to the actual point of the DosExecPgm
  15363.    call, is the reason for the elaborate procedure outlined above.
  15364.  
  15365.    The program EXECSORT.ASM in Figure 16-7 demonstrates this redirection of
  15366.    input and output for a filter run as a child process. EXECSORT makes
  15367.    duplicates of its standard input and standard output handles with
  15368.    DosDupHandle; then it redirects the standard input to the file MYFILE.DAT
  15369.    (which it opens) and the standard output to the file MYFILE.SRT (which it
  15370.    creates). EXECSORT then calls DosExecPgm to run the SORT.EXE filter that
  15371.    is supplied with OS/2.
  15372.  
  15373.    The SORT program reads the file MYFILE.DAT via its standard input handle,
  15374.    sorts the file alphabetically by line, and writes the sorted data to
  15375.    MYFILE.SRT via its standard output handle. When SORT terminates, OS/2
  15376.    closes SORT's inherited handles for the standard input and standard
  15377.    output, which forces an update of the directory entry for the file
  15378.    MYFILE.SRT. EXECSORT then resumes execution, restores its standard input
  15379.    and standard output handles (which are still open) to their original
  15380.    meanings with DosDupHandle, displays a success message on the standard
  15381.    output, and exits to OS/2.
  15382.  
  15383.    ──────────────────────────────────────────────────────────────────────────
  15384.            title     EXECSORT -- Run SORT.EXE as Child
  15385.            page      55,132
  15386.            .286
  15387.            .sall
  15388.  
  15389.    ;
  15390.    ; EXECSORT.ASM
  15391.    ;
  15392.    ; Demonstration of use of DosExecPgm to run the OS/2 filter SORT.EXE
  15393.    ; as a child process, redirecting its input to MYFILE.DAT and its
  15394.    ; output to MYFILE.SRT.
  15395.    ;
  15396.    ; Assemble with:  C> masm execsort.asm;
  15397.    ; Link with:  C> link execsort,,,os2,execsort
  15398.    ;
  15399.    ; Usage is:  C> execsort
  15400.    ;
  15401.    ; Copyright (C) 1988 Ray Duncan
  15402.    ;
  15403.  
  15404.    stdin   equ     0               ; standard input device
  15405.    stdout  equ     1               ; standard output device
  15406.    stderr  equ     2               ; standard error device
  15407.  
  15408.    cr      equ     0dh             ; ASCII carriage return
  15409.    lf      equ     0ah             ; ASCII linefeed
  15410.  
  15411.            extrn   DosClose:far
  15412.            extrn   DosDupHandle:far
  15413.            extrn   DosExecPgm:far
  15414.            extrn   DosExit:far
  15415.            extrn   DosOpen:far
  15416.            extrn   DosWrite:far
  15417.  
  15418.    jerr    macro   target          ;; Macro to test AX
  15419.            local   zero            ;; and jump if AX nonzero
  15420.            or      ax,ax
  15421.            jz      zero            ;; Uses JMP DISP16 to avoid
  15422.            jmp     target          ;; branch out of range errors
  15423.    zero:
  15424.            endm
  15425.  
  15426.  
  15427.    DGROUP  group   _DATA
  15428.    _DATA   segment word public 'DATA'
  15429.  
  15430.    iname   db      'MYFILE.DAT',0  ; name of input file
  15431.    oname   db      'MYFILE.SRT',0  ; name of output file
  15432.  
  15433.    ihandle dw      ?               ; handle for input file
  15434.    ohandle dw      ?               ; handle for output file
  15435.  
  15436.    action  dw      ?               ; receives DosOpen action
  15437.  
  15438.    oldin   dw      -1              ; dup of old stdin handle
  15439.    oldout  dw      -1              ; dup of old stdout handle
  15440.  
  15441.    newin   dw      stdin           ; forced to track ihandle
  15442.    newout  dw      stdout          ; forced to track ohandle
  15443.  
  15444.    pname   db      'SORT.EXE',0    ; pathname of SORT filter
  15445.  
  15446.    objbuff db      64              ; receives failing dynlink
  15447.    objbuff_len equ $-objbuff
  15448.  
  15449.    pcodes  dw      0,0             ; PID, exit code of child
  15450.  
  15451.    msg     db      cr,lf,'SORT was executed as child.',cr,lf
  15452.    msg_len equ $-msg
  15453.  
  15454.    _DATA   ends
  15455.  
  15456.  
  15457.    _TEXT   segment word public 'CODE'
  15458.  
  15459.            assume  cs:_TEXT,ds:DGROUP
  15460.    main    proc    far             ; entry point from OS/2
  15461.  
  15462.                                    ; prepare stdin and stdout
  15463.                                    ; handles for child SORT...
  15464.  
  15465.                                    ; dup handle for stdin...
  15466.            push    stdin           ; standard input handle
  15467.            push    ds              ; receives new handle
  15468.            push    offset DGROUP:oldin
  15469.            call    DosDupHandle    ; transfer to OS/2
  15470.            jerr    main1           ; exit if error
  15471.  
  15472.                                    ; dup handle for stdout...
  15473.            push    stdout          ; standard output handle
  15474.            push    ds              ; receives new handle
  15475.            push    offset DGROUP:oldout
  15476.            call    DosDupHandle    ; transfer to OS/2
  15477.            jerr    main1           ; exit if error
  15478.  
  15479.                                    ; open input file...
  15480.            push    ds              ; address of filename
  15481.            push    offset DGROUP:iname
  15482.            push    ds              ; receives file handle
  15483.            push    offset DGROUP:ihandle
  15484.            push    ds              ; receives DosOpen action
  15485.            push    offset DGROUP:action
  15486.            push    0               ; file size (not used)
  15487.            push    0
  15488.            push    0               ; attribute (not used)
  15489.            push    1               ; action: open if exists
  15490.                                    ;         fail if doesn't
  15491.            push    40h             ; access: read-only
  15492.            push    0               ; reserved DWORD 0
  15493.            push    0
  15494.            call    DosOpen         ; transfer to OS/2
  15495.            jerr    main1           ; exit if error
  15496.  
  15497.                                    ; create output file...
  15498.            push    ds              ; address of filename
  15499.            push    offset DGROUP:oname
  15500.            push    ds              ; receives file handle
  15501.            push    offset DGROUP:ohandle
  15502.            push    ds              ; receives DOSOPEN action
  15503.            push    offset DGROUP:action
  15504.            push    0               ; initial file size
  15505.            push    0
  15506.            push    0               ; attribute = normal
  15507.            push    12h             ; action: create/replace
  15508.            push    41h             ; access: write-only
  15509.            push    0               ; reserved DWORD 0
  15510.            push    0
  15511.            call    DosOpen         ; transfer to OS/2
  15512.            jerr    main1           ; exit if error
  15513.  
  15514.                                    ; make stdin track
  15515.                                    ; input file handle...
  15516.            push    ihandle         ; handle from DOSOPEN
  15517.            push    ds              ; standard input handle
  15518.            push    offset DGROUP:newin
  15519.            call    DosDupHandle    ; transfer to OS/2
  15520.            jerr    main1           ; exit if error
  15521.  
  15522.                                    ; make stdout track
  15523.                                    ; output file handle...
  15524.            push    ohandle         ; handle from DOSOPEN
  15525.            push    ds              ; standard output handle
  15526.            push    offset DGROUP:newout
  15527.            call    DosDupHandle    ; transfer to OS/2
  15528.            jerr    main1           ; exit if error
  15529.  
  15530.                                    ; run SORT.EXE as child...
  15531.            push    ds              ; receives failing dynlink
  15532.            push    offset DGROUP:objbuff
  15533.            push    objbuff_len     ; length of buffer
  15534.            push    0               ; 0 = synchronous execution
  15535.            push    0               ; argument strings pointer
  15536.            push    0
  15537.            push    0               ; environment pointer
  15538.            push    0
  15539.            push    ds              ; receives PID, exit code
  15540.            push    offset DGROUP:pcodes
  15541.            push    ds              ; child program pathname
  15542.            push    offset DGROUP:pname
  15543.            call    DosExecPgm      ; transfer to OS/2
  15544.            jerr    main1           ; exit if error
  15545.  
  15546.                                    ; restore stdin handle
  15547.                                    ; to original meaning...
  15548.            push    oldin           ; dup of original stdin
  15549.            push    ds              ; standard input handle
  15550.            push    offset DGROUP:newin
  15551.            call    DosDupHandle    ; transfer to OS/2
  15552.            jerr    main1           ; exit if error
  15553.                                    ; restore stdout handle
  15554.                                    ; to original meaning...
  15555.            push    oldout          ; dup of original stdout
  15556.            push    ds              ; standard output handle
  15557.            push    offset DGROUP:newout
  15558.            call    DosDupHandle    ; transfer to OS/2
  15559.            jerr    main1           ; exit if error
  15560.            push    oldin           ; close dup of stdin
  15561.            call    DosClose        ; transfer to OS/2
  15562.            jerr    main1           ; exit if error
  15563.  
  15564.            push    oldout          ; close dup of stdout
  15565.            call    DosClose        ; transfer to OS/2
  15566.            jerr    main1           ; exit if error
  15567.  
  15568.            push    ihandle         ; close input file
  15569.            call    DosClose        ; transfer to OS/2
  15570.            jerr    main1           ; exit if error
  15571.  
  15572.            push    ohandle         ; close output file
  15573.            call    DosClose        ; transfer to OS/2
  15574.            jerr    main1           ; exit if error
  15575.  
  15576.                                    ; display success message...
  15577.            push    stdout          ; standard output handle
  15578.            push    ds              ; address of message
  15579.            push    offset DGROUP:msg
  15580.            push    msg_len         ; length of message
  15581.            push    ds              ; receives bytes written
  15582.            push    offset DGROUP:action
  15583.            call    DosWrite        ; transfer to OS/2
  15584.            jerr    main1           ; exit if error
  15585.  
  15586.                                    ; exit point if no errors
  15587.            push    1               ; terminate all threads
  15588.            push    0               ; exit code = 0 (success)
  15589.            call    DosExit         ; transfer to OS/2
  15590.  
  15591.    main1:                          ; exit point if error
  15592.            push    1               ; terminate all threads
  15593.            push    1               ; exit code = 1 (error)
  15594.            call    DosExit
  15595.  
  15596.    main    endp
  15597.  
  15598.    _TEXT   ends
  15599.  
  15600.            end     main            ; defines entry point
  15601.    ──────────────────────────────────────────────────────────────────────────
  15602.  
  15603.    Figure 16-7.  Assembly language source code for EXECSORT.ASM, which
  15604.    demonstrates use of a filter as a child process. This program redirects
  15605.    the standard input and standard output handles to files, invokes
  15606.    DosExecPgm to run SORT.EXE as a child process, and then restores the
  15607.    original meaning of the standard input and standard output handles.
  15608.  
  15609.    To assemble the file EXECSORT.ASM into the file EXECSORT.OBJ, enter the
  15610.    following command:
  15611.  
  15612.    [C:\] MASM EXECSORT.ASM;  <Enter>
  15613.  
  15614.    To build the executable file EXECSORT.EXE from the files EXECSORT.OBJ,
  15615.    EXECSORT.DEF (Figure 16-8), and the import library OS2.LIB, enter the
  15616.    following command:
  15617.  
  15618.    [C:\] LINK EXECSORT,,,OS2,EXECSORT  <Enter>
  15619.  
  15620.    ──────────────────────────────────────────────────────────────────────────
  15621.    NAME EXECSORT NOTWINDOWCOMPAT
  15622.    PROTMODE
  15623.    STACKSIZE 4096
  15624.    ──────────────────────────────────────────────────────────────────────────
  15625.  
  15626.    Figure 16-8.   EXECSORT.DEF, the module definition for EXECSORT.EXE.
  15627.  
  15628.  
  15629.  
  15630.  ────────────────────────────────────────────────────────────────────────────
  15631.  Chapter 17  Device Drivers
  15632.  
  15633.    Device drivers are OS/2 modules that issue commands directly to peripheral
  15634.    devices and cause data to be transferred between those devices and RAM.
  15635.    They are thus the most hardware-dependent layer of OS/2. Drivers shield
  15636.    the operating system kernel from the need to deal with hardware I/O port
  15637.    addresses and variable characteristics (such as number of tracks per disk
  15638.    or sectors per track) of peripheral devices, in the same way that the
  15639.    kernel in turn shields application programs from the details of file and
  15640.    memory management.
  15641.  
  15642.    By convention, OS/2 device drivers reside in individual disk files that
  15643.    have the extension SYS. The essential or base drivers──the keyboard,
  15644.    display, disk, printer, and clock drivers──are always loaded during the
  15645.    boot process. Other, optional device drivers can be loaded by DEVICE
  15646.    directives in the CONFIG.SYS file and are referred to as installable
  15647.    drivers. In reality, however, all OS/2 drivers have the same physical and
  15648.    logical structure and the same interface to the OS/2 kernel, and they must
  15649.    all be incorporated into the system at boot time.
  15650.  
  15651.    OS/2 device drivers are similar in many ways to both MS-DOS and XENIX/UNIX
  15652.    device drivers, but they have a number of unique capabilities and
  15653.    requirements. The experienced MS-DOS programmer, in particular, must be
  15654.    wary of terminology that sounds familiar──the associated driver function
  15655.    might be vastly different. A full-fledged OS/2 driver for any device is
  15656.    invariably more complex than its MS-DOS equivalent.
  15657.  
  15658.  
  15659.  Unique Aspects of OS/2 Drivers
  15660.  
  15661.    Six features of OS/2 device drivers have no counterpart under MS-DOS:
  15662.  
  15663.    ■  The ability to handle more than one request at a time
  15664.  
  15665.    ■  The availability of OS/2 kernel services and inter-driver communication
  15666.  
  15667.    ■  Bimodal operation
  15668.  
  15669.    ■  Support for IBM ROM BIOS─equivalent services to real mode applications
  15670.  
  15671.    ■  Support for device monitors
  15672.  
  15673.    ■  The ability to "deinstall" on demand
  15674.  
  15675.    MS-DOS is a single-tasking operating system, and under normal
  15676.    circumstances, a maximum of one I/O operation can be in progress.
  15677.    Consequently, MS-DOS drivers are easy to write because the code need not
  15678.    be reentrant and polled I/O can be used whenever it is convenient. In
  15679.    OS/2, however, multiple processes can be active simultaneously, and each
  15680.    process can contain multiple threads that are issuing I/O requests
  15681.    independently. A device driver must, therefore, be reentrant and must be
  15682.    able to manage overlapping I/O requests for the same device. And to keep
  15683.    the cost in CPU cycles to a minimum, the driver must take full advantage
  15684.    of the interrupt and DMA capabilities of its device.
  15685.  
  15686.    OS/2's multitasking character does, however, yield some benefits for
  15687.    drivers. Under MS-DOS, device drivers must be self-contained. If, at the
  15688.    same time that it is carrying out an I/O request from the kernel, an
  15689.    MS-DOS driver requests a different operation from the kernel, the context
  15690.    of the original request is destroyed, and the system crashes. In contrast,
  15691.    most OS/2 kernel services are fully reentrant, and OS/2 can provide a
  15692.    battery of services to device drivers much as it does to application
  15693.    programs (although the nature of the services is, of course, much
  15694.    different). These services are called Device Driver Helpers, or DevHlp
  15695.    functions. In OS/2 version 1.1 drivers can also call each other directly
  15696.    through inter-driver communication (IDC) entry points.
  15697.  
  15698.    The term bimodal operation stems from OS/2's requirement that its device
  15699.    drivers be capable of servicing both I/O requests and hardware interrupts
  15700.    in either real mode or protected mode. Bimodality improves performance by
  15701.    reducing the number of mode switches──for example, if the CPU is in real
  15702.    mode at the completion of an operation that was started in protected mode,
  15703.    the driver need not switch back to protected mode to service an interrupt.
  15704.    OS/2 assists the driver to achieve bimodal operation by providing it with
  15705.    special bimodal pointers and other services, which will be discussed later
  15706.    in the chapter.
  15707.  
  15708.    OS/2 drivers must contain ROM BIOS support because many MS-DOS
  15709.    applications call the ROM BIOS directly rather than performing all their
  15710.    I/O through documented MS-DOS services──either to obtain increased
  15711.    performance or to gain functionality that MS-DOS does not offer (EGA/VGA
  15712.    palette programming, for example). To support such applications in DOS
  15713.    compatibility mode, an OS/2 driver must capture the software interrupt
  15714.    vectors that are used to invoke ROM BIOS services for its device. It can
  15715.    then either interlock the ROM BIOS routines to prevent interference with
  15716.    the execution of protected mode applications, or substitute a new set of
  15717.    routines that provide equivalent services.
  15718.  
  15719.    Device monitors are an OS/2 innovation that allow you to write and install
  15720.    keyboard enhancers, macro-expanders, print spoolers, and similar utilities
  15721.    in a hardware-independent manner. An application program that wants to
  15722.    filter the data stream of a particular character device driver can
  15723.    register as a monitor for that device. Acting as the intermediary, the
  15724.    OS/2 kernel asks the driver whether it wants to accept the monitor
  15725.    registration. If the driver consents, it cooperates with the kernel to
  15726.    pass each character that it reads from or writes to the device through a
  15727.    buffer belonging to the monitor program, which can add, delete, or
  15728.    substitute characters at its discretion. Device monitors are discussed in
  15729.    detail in Chapter 18.
  15730.  
  15731.    Last, OS/2 driver support for deinstallation allows efficient use of
  15732.    memory and a more controlled environment than MS-DOS. Under MS-DOS, you
  15733.    can supersede a character device driver simply by installing another
  15734.    driver with the same logical device name; the first driver loaded has no
  15735.    way to prevent being superseded, and the memory it occupied is simply
  15736.    lost. Under OS/2, if a driver is loaded that has the same logical name as
  15737.    a previously loaded driver, the kernel asks the previous driver whether it
  15738.    is willing to be replaced. The first driver can agree to the request and
  15739.    release any interrupts and other system resources that it owns, after
  15740.    which its memory is reclaimed and the new driver is allowed to initialize
  15741.    itself. Alternatively, the first driver can refuse to deinstall, and the
  15742.    kernel then terminates installation of the new driver.
  15743.  
  15744.  
  15745.  Device Driver Types
  15746.  
  15747.    OS/2 device drivers are categorized into two groups: block device drivers
  15748.    and character device drivers. A driver's membership in one of these groups
  15749.    determines the way OS/2 views the associated device and the particular
  15750.    functions the driver itself must support.
  15751.  
  15752.    Character device drivers control peripheral devices, such as terminals or
  15753.    printers, that perform input and output operations one character (or byte)
  15754.    at a time. A particular character device driver ordinarily supports a
  15755.    single hardware unit, although a given SYS file can contain more than one
  15756.    character device driver. Each character device has a 1- to 8-character
  15757.    logical name; an application program can "open" the device by name for
  15758.    input or output (or both) as though it were a file. The logical name is
  15759.    strictly a means of identifying the driver to OS/2 and has no physical
  15760.    equivalent on the device.
  15761.  
  15762.    Block device drivers control peripheral devices that transfer data in
  15763.    chunks rather than byte by byte. Block devices are usually randomly
  15764.    addressable devices, such as disk drives, but they can also be sequential
  15765.    in nature, such as magnetic tape drives. A block driver can support more
  15766.    than one physical unit and can also map two or more logical units onto a
  15767.    single physical unit (such as a partitioned fixed disk).
  15768.  
  15769.    OS/2 assigns single-letter drive identifiers (A, B, and so forth) to block
  15770.    devices, instead of logical names. The letter assigned to a given block
  15771.    device is determined solely by the order in which the block drivers are
  15772.    loaded. The total number of letters assigned to a driver is determined by
  15773.    the number of logical units that the driver supports.
  15774.  
  15775.  
  15776.  Device Driver Modes
  15777.  
  15778.    Whenever an OS/2 device driver has control of the CPU, it is said to be
  15779.    executing in one of four different modes: kernel mode, interrupt mode,
  15780.    user mode, or init mode. Understanding these modes and their implications
  15781.    is vital to writing an OS/2 device driver because the current mode
  15782.    determines the kernel services (DevHlps) available to a driver and the
  15783.    actions that the driver can take.
  15784.  
  15785.    A driver is in kernel mode when the kernel calls the driver's strategy
  15786.    entry point (explained later) to obtain information or to start an I/O
  15787.    operation, usually as the immediate result of an API call by an
  15788.    application. In kernel mode, the driver executes in ring 0 with nearly
  15789.    every DevHlp service available to it.
  15790.  
  15791.    A driver executes in interrupt mode as the result of a hardware interrupt.
  15792.    The interrupt is initially fielded by the kernel, which saves all
  15793.    registers, sets DS to point to the driver's data segment, and then
  15794.    transfers control to the driver's interrupt handler. In interrupt mode,
  15795.    the driver also executes in ring 0 but has only a subset of DevHlp
  15796.    services available.
  15797.  
  15798.    The driver executes in user mode when it is entered as a result of a
  15799.    software interrupt by an application executing in the DOS compatibility
  15800.    environment. The only drivers that must concern themselves with this mode
  15801.    are those that must provide IBM ROM BIOS─equivalent services in a manner
  15802.    consistent with the multitasking and memory protection requirements of
  15803.    OS/2. Because a driver runs in user mode only when the CPU is in real
  15804.    mode, the concept of privilege levels is irrelevant, but you can think of
  15805.    the driver as executing in ring 0 because no protection mechanisms
  15806.    prevail. Relatively few DevHlp services are available to the driver in
  15807.    user mode.
  15808.  
  15809.    A driver runs in init mode only once──when the kernel calls the driver to
  15810.    initialize itself immediately after the driver is loaded during the boot
  15811.    process. In init mode, the driver runs as though it were an application
  15812.    program with I/O privilege (IOPL). A limited set of dynlink API calls, as
  15813.    well as a subset of the DevHlp services, are available to a driver in init
  15814.    mode.
  15815.  
  15816.    Two additional terms that pertain to a driver's execution mode are task
  15817.    time and interrupt time. A driver is executing at task time when it is
  15818.    called synchronously──as the direct result of an application API request
  15819.    or a real mode software interrupt; this context would include both the
  15820.    user mode and kernel mode, as described above. Interrupt time refers to
  15821.    the asynchronous invocation of a driver routine──as the result of an
  15822.    external hardware event. At interrupt time, the current CPU mode, process,
  15823.    and LDT are unpredictable.
  15824.  
  15825.  
  15826.  Device Driver Structure
  15827.  
  15828.    An OS/2 device driver has three major components: the device driver
  15829.    header, the strategy routine, and the interrupt routine.
  15830.  
  15831.  The Device Driver Header
  15832.  
  15833.    The device driver header always appears at the start of the device
  15834.    driver's near data segment. The header contains information about the
  15835.    driver that the OS/2 kernel can inspect when it needs to satisfy an
  15836.    application program's I/O request (Figure 17-1 on the following page).
  15837.  
  15838.    The first element of the header is a pointer to the next driver in the
  15839.    system's chain of all device drivers. This pointer is initialized to -1,
  15840.    -1; the system fills in the true value when the driver is loaded. If you
  15841.    include multiple drivers and headers in a single file, link the headers
  15842.    together and initialize only the last header's link field to -1, -1.
  15843.  
  15844.    00H  ┌──────────────────────────────────────┐
  15845.         │  Link to next driver header, offset  │
  15846.    02H  ├──────────────────────────────────────┤
  15847.         │ Link to next driver header, selector │
  15848.    04H  ├──────────────────────────────────────┤
  15849.         │        Device attribute word         │
  15850.    06H  ├──────────────────────────────────────┤
  15851.         │     Offset, strategy entry point     │
  15852.    08H  ├──────────────────────────────────────┤
  15853.         │       Offset, IDC entry point        │
  15854.    0AH  ├──────────────────────────────────────┤
  15855.         │                                      │
  15856.        ─┴─                Name                ─┴─
  15857.        ─┬─                                    ─┬─
  15858.         │                                      │
  15859.    12H  ├──────────────────────────────────────┤
  15860.         │                                      │
  15861.        ─┴─              Reserved              ─┴─
  15862.        ─┬─                                    ─┬─
  15863.         │                                      │
  15864.    1AH  └──────────────────────────────────────┘
  15865.  
  15866.    Figure 17-1.  OS/2 device driver header. In character device drivers, the
  15867.    Name field contains the logical name of the device padded with spaces if
  15868.    necessary to a length of 8 bytes. In block device drivers, the first byte
  15869.    of the Name field contains the number of logical units supported by the
  15870.    driver (filled in by OS/2); the remaining 7 bytes of the name field are
  15871.    not used.
  15872.  
  15873.    The other important components of the header are a word (16 bits) of
  15874.    device driver attribute flags, the offsets of the driver's strategy
  15875.    routine and IDC entry point, and the logical device name (for a character
  15876.    device such as PRN or COM1) or the number of logical units (for a block
  15877.    device). When present, the device name must be left-justified, all
  15878.    uppercase, and padded with spaces to a length of eight characters.
  15879.  
  15880.    The device attribute word in the header (Figure 17-2) defines the
  15881.    driver's function level and indicates whether it controls a character or a
  15882.    block device, supports certain optional driver functions, and reads or
  15883.    writes IBM-compatible disk media. The least significant four bits
  15884.    determine whether OS/2 should use that driver as the standard input,
  15885.    standard output, clock, or NUL device; as you might expect, each of these
  15886.    four bits should be set on only one driver at a time.
  15887.  
  15888.    Examples of source code for device driver headers appear in Figures 17-3,
  15889.    17-4, and 17-5, which are all on p. 354.
  15890.  
  15891.     F  E  D  C  B  A  9  8  7  6  5  4  3  2  1  0
  15892.    ┌──┬──┬──┬──┬──┬──┬───────┬─────────┬──┬──┬──┬──┐
  15893.    │  │  │  │  │  │  │       │         │  │  │  │  │
  15894.    └┬─┴┬─┴┬─┴┬─┴┬─┴┬─┴───┬───┴────┬────┴┬─┴┬─┴┬─┴┬─┘
  15895.     │  │  │  │  │  │     │        │     │  │  │  │
  15896.     │  │  │  │  │  │     │        │     │  │  │  └─ 1 = standard input
  15897.     │  │  │  │  │  │     │        │     │  │  └──── 1 = standard output
  15898.     │  │  │  │  │  │     │        │     │  └─────── 1 = NUL device
  15899.     │  │  │  │  │  │     │        │     └────────── 1 = clock device
  15900.     │  │  │  │  │  │     │        └──────────────── Reserved
  15901.     │  │  │  │  │  │     └───────────────────────── Function level
  15902.     │  │  │  │  │  │                                001 for OS/2 1.0 and 1.1
  15903.     │  │  │  │  │  └─────────────────────────────── Reserved
  15904.     │  │  │  │  └────────────────────────────────── 0 = Open/Close/RM
  15905.     │  │  │  │                                          not supported
  15906.     │  │  │  │                                      1 = Open/Close/RM
  15907.     │  │  │  │                                          supported
  15908.     │  │  │  └───────────────────────────────────── 0 = sharper ignores
  15909.     │  │  │                                             competing opens
  15910.     │  │  │                                         1 = sharper controls
  15911.     │  │  │                                             device contention
  15912.     │  │  │                                             (character devices
  15913.     │  │  │                                             only)
  15914.     │  │  └──────────────────────────────────────── 0 = IBM-compatible
  15915.     │  │                                                (FAT ID byte valid)
  15916.     │  │                                            1 = non-IBM-compatible
  15917.     │  │                                                disk format
  15918.     │  └─────────────────────────────────────────── 0 = inter-driver
  15919.     │                                                   communication (IDC)
  15920.     │                                                   not supported
  15921.     │                                               1 = IDC supported
  15922.     └────────────────────────────────────────────── 0 = block device
  15923.                                                     1 = character device
  15924.  
  15925.    Figure 17-2.  The device attribute word in the device driver header, which
  15926.    describes the characteristics and capabilities of the associated driver.
  15927.  
  15928.    ──────────────────────────────────────────────────────────────────────────
  15929.    header  dd      -1              ; link to next driver
  15930.            dw      8880h           ; device attribute word
  15931.                                    ; bit 15 = 1 character device
  15932.                                    ; bit 11 = 1 Open/Close/RM
  15933.                                    ; bits 7-9 = 001 driver level
  15934.            dw      Strat           ; strategy entry point
  15935.            dw      0               ; IDC entry point
  15936.            db      'COM1    '      ; logical device name
  15937.            db      8 dup (0)       ; (reserved)
  15938.    ──────────────────────────────────────────────────────────────────────────
  15939.  
  15940.    Figure 17-3.  Source code for an OS/2 character device driver header.
  15941.  
  15942.    ──────────────────────────────────────────────────────────────────────────
  15943.    header  dd      -1              ; link to next driver
  15944.            dw      0880h           ; device attribute word
  15945.                                    ; bit 15 = 0 block device
  15946.                                    ; bit 11 = 1 Open/Close/RM
  15947.                                    ; bits 7-9 = 001 driver level
  15948.            dw      Strat           ; strategy entry point
  15949.            dw      0               ; (reserved)
  15950.            db      0               ; units (filled in by OS/2)
  15951.            db      15 dup (0)      ; (reserved)
  15952.    ──────────────────────────────────────────────────────────────────────────
  15953.  
  15954.    Figure 17-4.  Source code for an OS/2 block device driver header.
  15955.  
  15956.    ──────────────────────────────────────────────────────────────────────────
  15957.                                    ; header for first device...
  15958.    head1   dd      head2           ; link to next driver
  15959.            dw      8880h           ; device attribute word
  15960.            dw      Strat1          ; strategy entry point
  15961.            dw      0               ; IDC entry point
  15962.            db      'COM1    '      ; logical device name
  15963.            db      8 dup (0)       ; (reserved)
  15964.  
  15965.                                    ; header for second device...
  15966.    head2   dd      -1              ; link to next driver
  15967.            dw      8880h           ; device attribute word
  15968.            dw      Strat2          ; strategy entry point
  15969.            dw      0               ; IDC entry point
  15970.            db      'COM2    '      ; logical device name
  15971.            db      8 dup (0)       ; (reserved)
  15972.    ──────────────────────────────────────────────────────────────────────────
  15973.  
  15974.    Figure 17-5.  Source code demonstrating the linkage of two device headers
  15975.    in the same driver file.
  15976.  
  15977.  The Strategy Routine
  15978.  
  15979.    The OS/2 kernel initiates an I/O operation by calling the driver's
  15980.    strategy routine (whose offset is in the device driver header) with the
  15981.    address of a data structure called a request packet in registers ES:BX.
  15982.    The first 13 bytes of the request packet have a fixed format and are known
  15983.    as the static portion; they are followed by data that varies according to
  15984.    the operation being requested (Figure 17-6). To support multiple
  15985.    concurrent I/O requests, the driver maintains a private queue of request
  15986.    packets for its pending operations with the aid of the kernel DevHlp
  15987.    functions.
  15988.  
  15989.    Note that the system passes the request packet address in the form of a
  15990.    bimodal pointer. In a bimodal pointer, the protected mode selector is
  15991.    identical to the corresponding real mode segment so the same address is
  15992.    valid in either real mode or protected mode. As a general rule, OS/2 can
  15993.    generate bimodal pointers only for data structures that it creates or owns
  15994.    (such as request packets) because the data must be located in the first
  15995.    640 KB of memory.
  15996.  
  15997.    00H  ┌───────────────────────────────────────┐
  15998.         │       Length of request packet        │
  15999.    01H  ├───────────────────────────────────────┤
  16000.         │       Block device unit number        │
  16001.    02H  ├───────────────────────────────────────┤
  16002.         │   Command code (driver subfunction)   │
  16003.    03H  ├───────────────────────────────────────┤
  16004.         │   Status word (returned to kernel)    │
  16005.    05H  ├───────────────────────────────────────┤
  16006.        ─┴─                                     ─┴─
  16007.        ─┬─            Reserved area            ─┬─
  16008.    09H  ├───────────────────────────────────────┤
  16009.        ─┴─                                     ─┴─
  16010.        ─┬─            Queue linkage            ─┬─
  16011.    0DH  ├───────────────────────────────────────┤
  16012.        ─┴─        Command-specific data        ─┴─
  16013.        ─┬─           (length varies)           ─┬─
  16014.         └───────────────────────────────────────┘
  16015.  
  16016.    Figure 17-6.  A prototypal OS/2 device driver request packet. The OS/2
  16017.    kernel requests a driver operation by passing the bimodal address of a
  16018.    request packet containing operation-specific parameters, and the driver
  16019.    communicates the results of the operation to the kernel by placing the
  16020.    status and other information in the same packet.
  16021.  
  16022.    The crucial fields of the request packet (in the static portion) are the
  16023.    command code, which selects a particular driver function, and the status
  16024.    word (Figure 17-7), which is the primary means by which the driver
  16025.    returns information to the kernel. The driver can carry out many functions
  16026.    immediately──at the time of the strategy call; in such cases, it simply
  16027.    places any necessary information into the request packet, sets the Done
  16028.    bit in the status word, and returns to the kernel. If it encounters an
  16029.    error, the driver also sets the Error bit in the status word, along with
  16030.    the appropriate code for the error type (Figure 17-8).
  16031.  
  16032.    However, some driver functions, such as Read and Write, involve an
  16033.    unpredictable amount of delay and cannot be carried out during the
  16034.    strategy call without interfering with multitasking; they must be
  16035.    completed with the aid of the interrupt routine (discussed below). If the
  16036.    device is idle, the strategy routine starts the I/O operation and then
  16037.    returns control to the kernel so that other work in the system can
  16038.    proceed. If the device is busy, the new request packet is instead chained
  16039.    onto the driver's request queue or work list. Incidentally, a block device
  16040.    driver is expected to sort its work list in such a way that the average
  16041.    transfer time is minimized. (A kernel DevHlp is available to perform this
  16042.    sorting.)
  16043.  
  16044.     F  E  D  C  B  A  9  8  7  6  5  4  3  2  1  0
  16045.    ┌──┬──┬───────────┬──┬──┬───────────────────────┐
  16046.    │  │  │           │  │  │                       │
  16047.    └┬─┴┬─┴─────┬─────┴┬─┴┬─┴───────────┬───────────┘
  16048.     │  │       │      │  │             └───────────── Error code,
  16049.     │  │       │      │  │                            if bit 15 = 1
  16050.     │  │       │      │  │
  16051.     │  │       │      │  └─────────────────────────── 0 = not done
  16052.     │  │       │      │                               1 = done
  16053.     │  │       │      │
  16054.     │  │       │      └────────────────────────────── 0 = not busy
  16055.     │  │       │                                      1 = busy
  16056.     │  │       │
  16057.     │  │       └───────────────────────────────────── Reserved (0)
  16058.     │  │
  16059.     │  └───────────────────────────────────────────── 0 = normal error codes
  16060.     │                                                 1 = error codes are
  16061.     │                                                     driver-specific
  16062.     │
  16063.     └──────────────────────────────────────────────── 0 = no error
  16064.                                                       1 = error
  16065.  
  16066.    Figure 17-7.  The request packet status word returned to the kernel by the
  16067.    driver after an operation is completed.
  16068.  
  16069.    Error Code                Description
  16070.    ──────────────────────────────────────────────────────────────────────────
  16071.    00H                       Write-protect violation
  16072.    01H                       Unknown unit
  16073.    02H                       Device not ready
  16074.    03H                       Unknown command
  16075.    04H                       CRC error
  16076.    05H                       Bad drive request structure length
  16077.    06H                       Seek error
  16078.    07H                       Unknown medium
  16079.    08H                       Sector not found
  16080.    09H                       Printer out of paper
  16081.    0AH                       Write fault
  16082.    0BH                       Read fault
  16083.    0CH                       General failure
  16084.    0DH                       Change disk
  16085.    0EH                       Reserved
  16086.    0FH                       Reserved
  16087.    10H                       Uncertain medium
  16088.    11H                       Character I/O call interrupted
  16089.    12H                       Monitors not supported
  16090.    13H                       Invalid parameter
  16091.    ──────────────────────────────────────────────────────────────────────────
  16092.  
  16093.    Figure 17-8.  The error codes used in the request packet status word.
  16094.    Error 0DH can be returned by drivers that map more than one logical unit
  16095.    onto the same physical drive. It causes the kernel to prompt the user to
  16096.    switch disks.
  16097.  
  16098.    A driver should return error 10H (Uncertain medium) when a drive-not-ready
  16099.    condition is detected, when accessing removable media without change-line
  16100.    support and a delay of more than two seconds occurs between accesses, or
  16101.    when accessing a drive with change-line support and the change-line
  16102.    indicates that the disk might have been replaced. The driver should
  16103.    continue to return error 10H for all requests until it receives a Reset
  16104.    Media request packet (command code 17).
  16105.  
  16106.  The Interrupt Routine
  16107.  
  16108.    The interrupt routine is entered from the kernel as a result of a hardware
  16109.    interrupt from the physical device, when an I/O operation either has been
  16110.    completed or has been aborted because of a hardware error. Because
  16111.    hardware interrupts are by nature asynchronous and unpredictable, an OS/2
  16112.    device driver must be capable of servicing an interrupt properly in either
  16113.    protected mode or real mode. The interrupt routine queries the peripheral
  16114.    controller to determine the outcome of the I/O operation, sets the
  16115.    appropriate status and error information in the request packet, calls the
  16116.    DevHlp routine DevDone to signal the kernel that the I/O is complete, and
  16117.    removes the request packet from its request queue.
  16118.  
  16119.    The interrupt routine then examines the request queue to determine whether
  16120.    additional requests are pending for the device. If the request queue is
  16121.    not empty, the interrupt routine removes the request packet from the head
  16122.    of the queue and starts the next I/O operation. It then clears the Carry
  16123.    flag and exits to the kernel with a far return.
  16124.  
  16125.    If multiple devices (and their drivers) share the same interrupt level,
  16126.    the kernel calls each driver's interrupt routine in turn until one
  16127.    indicates its acceptance of the interrupt by clearing the Carry flag. The
  16128.    other interrupt routines must return with the Carry flag set.
  16129.  
  16130.  
  16131.  The Command Code Functions
  16132.  
  16133.    A total of 27 command codes are defined for OS/2 device drivers, some of
  16134.    which are reserved for future expansion or for backward compatibility with
  16135.    MS-DOS drivers. The command codes and the names of their associated
  16136.    functions are shown in Figure 17-9.
  16137.  
  16138.    Some command codes are relevant only for character drivers and some only
  16139.    for block device drivers; a few are used in both. Whichever type of driver
  16140.    you are writing, you need to supply an executable routine for each command
  16141.    code, even if the routine does nothing but set the Done bit in the request
  16142.    packet status word. The following pages describe each of the command codes
  16143.    in detail.
  16144.  
  16145. ╓┌─┌─────────┌─────────────┌───────────────────────┌─────────────┌───────────╖
  16146.    Command   Hex           Function Name           Character     Block
  16147.    Code      Value                                 Devices       Devices
  16148.    ──────────────────────────────────────────────────────────────────────────
  16149.     0        00H           Init (Initialization)   C             B
  16150.     1        01H           Media Check                           B
  16151.    Command   Hex           Function Name           Character     Block
  16152.    Code      Value                                 Devices       Devices
  16153.    ──────────────────────────────────────────────────────────────────────────
  16154.    1        01H           Media Check                           B
  16155.     2        02H           Build BPB                             B
  16156.     3        03H           Reserved
  16157.     4        04H           Read (Input)            C             B
  16158.     5        05H           Nondestructive Read     C
  16159.     6        06H           Input Status            C
  16160.     7        07H           Flush Input Buffers     C
  16161.     8        08H           Write (Output)          C             B
  16162.     9        09H           Write with Verify       C             B
  16163.    10        0AH           Output Status           C
  16164.    11        0BH           Flush Output Buffers    C
  16165.    12        0CH           Reserved
  16166.    13        0DH           Device Open             C             B
  16167.    14        0EH           Device Close            C             B
  16168.    15        0FH           Removable Media                       B
  16169.    16        10H           Generic IOCtl           C             B
  16170.    17        11H           Reset Media                           B
  16171.    18        12H           Get Logical Drive Map                 B
  16172.    Command   Hex           Function Name           Character     Block
  16173.    Code      Value                                 Devices       Devices
  16174.    ──────────────────────────────────────────────────────────────────────────
  16175.   18        12H           Get Logical Drive Map                 B
  16176.    19        13H           Set Logical Drive Map                 B
  16177.    20        14H           DeInstall Driver        C
  16178.    21        15H           Reserved
  16179.    22        16H           Partitionable Fixed                   B
  16180.                            Disk
  16181.    23        17H           Get Fixed Disk Map                    B
  16182.    24        18H           Reserved
  16183.    25        19H           Reserved
  16184.    26        1AH           Reserved
  16185.    ──────────────────────────────────────────────────────────────────────────
  16186.  
  16187.  
  16188.    Figure 17-9.  The strategy routine functions selected by the command code
  16189.    field of the request packet.
  16190.  
  16191.  The Init Function
  16192.  
  16193.    The Init (Initialization) routine (command code 0) for a driver is called
  16194.    only once──when the driver is loaded. It must ensure that the peripheral
  16195.    device controlled by the driver is present and functional, perform any
  16196.    necessary hardware initialization (such as a reset on a printer), register
  16197.    with the system for any interrupts that the driver will need later, and
  16198.    initialize driver data structures (such as semaphores or the request
  16199.    queue). The parameters and results for this command code are summarized in
  16200.    Figure 17-10 on the following page.
  16201.  
  16202.    The kernel passes two addresses to the driver in the Init request packet:
  16203.    a bimodal pointer to the DevHlp common entry point (discussed in detail
  16204.    later) and a pointer to the variable part of the DEVICE= line (from the
  16205.    CONFIG.SYS file) that caused the driver to be loaded. The line is
  16206.    read-only and is terminated by a null (zero) byte; the driver can scan it
  16207.    for switches or other parameters that may influence its operation. For
  16208.    example, if the driver TINYDISK.SYS is loaded with the following line in
  16209.    the CONFIG.SYS file:
  16210.  
  16211.    DEVICE=tinydisk.sys 1024K
  16212.  
  16213.    the request packet contains a pointer to the following sequence of bytes:
  16214.  
  16215.    74 69 6E 79 64 69 73 6B 2E 73 79 73 20 31 30 32 34 4B 00
  16216.  
  16217.    Block drivers are also passed the drive code that will be assigned to
  16218.    their first logical unit (0 = A, 1 = B, and so forth).
  16219.  
  16220.         Driver called with...           Driver returns...
  16221.    00H ┌────────────────────┐   00H ┌────────────────────────┐
  16222.        │       Length       │       │                        │
  16223.    01H ├────────────────────┤   01H ├────────────────────────┤
  16224.        │                    │       │                        │
  16225.    02H ├────────────────────┤   02H ├────────────────────────┤
  16226.        │    Command code    │       │                        │
  16227.    03H ├────────────────────┤   03H ├────────────────────────┤
  16228.        │                    │       │         Status         │
  16229.    05H ├────────────────────┤   05H ├────────────────────────┤
  16230.        │      Reserved      │       │                        │
  16231.    09H ├────────────────────┤   09H ├────────────────────────┤
  16232.        │    Queue linkage   │       │                        │
  16233.    0DH ├────────────────────┤   0DH ╔════════════════════════╗
  16234.        │                    │       ║     Logical units      ║
  16235.    0EH ├────────────────────┤   0EH ╚════════════════════════╝
  16236.        │                    │       │ Length of code segment │
  16237.        │ DevHlp entry point │   10H ├────────────────────────┤
  16238.        │                    │       │ Length of data segment │
  16239.    12H ├────────────────────┤   12H ╔════════════════════════╗
  16240.        │     Pointer to     │       ║     Address of BPB     ║
  16241.        │   INIT arguments   │       ║     pointer array      ║
  16242.    16H ╔════════════════════╗   16H ╚════════════════════════╝  ╔══╗ Block
  16243.        ║     First unit     ║       │       First unit       │  ╚══╝ devices
  16244.    17H ╚════════════════════╝   17H └────────────────────────┘       only
  16245.  
  16246.    Figure 17-10.  Request packet format for the Init function (command code
  16247.    0).
  16248.  
  16249.    The Init function must return the status and the amount of memory that it
  16250.    wants to reserve for the driver's code and data segments. This allocation
  16251.    allows the driver to discard initialization code and data (thus minimizing
  16252.    its memory requirements) by positioning these items at the end of their
  16253.    respective segments. A block device driver must also return the number of
  16254.    logical units it supports and the address of a BPB pointer array.
  16255.  
  16256.    The number of units returned by a block driver is used to assign device
  16257.    identifiers. Suppose, for example, drivers are already present for four
  16258.    block devices (drive codes 0 through 3, corresponding to drive identifiers
  16259.    A through D). If a new driver is initialized that supports four units, it
  16260.    will be assigned the drive numbers 4 through 7 (corresponding to the drive
  16261.    names E through H). Although the device driver header also contains a
  16262.    field for the number of units, OS/2 does not inspect that field during
  16263.    initialization; instead, the system sets that header field using
  16264.    information returned by the Init function.
  16265.  
  16266.    The BPB pointer array is an array of word offsets to BIOS parameter blocks
  16267.    (Figure 17-11). The array must contain one entry for each logical unit
  16268.    that the driver supports, although all entries can point to the same BPB
  16269.    to conserve memory. During the boot sequence, OS/2 scans the BPBs of every
  16270.    block device to determine the largest sector size used in the system; it
  16271.    then uses this information to set the size of the buffers in the system's
  16272.    disk cache.
  16273.  
  16274.    Offset      Length       Contents
  16275.    ──────────────────────────────────────────────────────────────────────────
  16276.    00H         2            Bytes per sector
  16277.    02H         1            Sectors per allocation unit (cluster), always a
  16278.                             power of two
  16279.    03H         2            Reserved sectors (starting at logical sector 0)
  16280.    05H         1            Number of file allocation tables
  16281.    06H         2            Number of root directory entries
  16282.    08H         2            Number of sectors in logical volume (including
  16283.                             boot sector, FAT, etc.)
  16284.    0AH         1            Medium descriptor byte
  16285.    0BH         2            Number of sectors per FAT
  16286.    0DH         2            Sectors per track
  16287.    0FH         2            Number of heads
  16288.    11H         4            Number of hidden sectors before reserved sectors
  16289.    15H         4            Total sectors in logical volume (if word at
  16290.                             offset 08H = 0)
  16291.    19H         6            Reserved
  16292.    ──────────────────────────────────────────────────────────────────────────
  16293.  
  16294.    Figure 17-11.  Structure of BIOS parameter block (BPB).
  16295.  
  16296.    If the Init routine finds that the driver's hardware device is missing or
  16297.    defective, it can cancel driver installation by returning the following
  16298.    values in the request packet: zero for the number of units, zero for the
  16299.    code and data segment lengths, and a status of 810CH (Error flag, Done
  16300.    flag, and the error code for a general failure).
  16301.  
  16302.    If the driver's data segment (which has a maximum size of 64 KB) is not
  16303.    large enough, the Init routine can call the DevHlp function AllocPhys to
  16304.    reserve additional memory. This additional memory can be either above or
  16305.    below the 1 MB boundary and is not movable or swappable. The driver itself
  16306.    is always loaded in the first 640 KB of memory. When the driver is called
  16307.    in real mode, it has faster access to memory allocated below 1 MB than to
  16308.    memory above that boundary, but allocating low memory decreases the amount
  16309.    of space available for a program running in DOS compatibility mode. In
  16310.    general, a driver should keep data items to which it needs frequent and
  16311.    rapid access in its near data segment or in additional memory allocated
  16312.    below 1 MB, and it should put large or infrequently used data structures
  16313.    and buffers above 1 MB.
  16314.  
  16315.    Unlike the other command code routines, which run in kernel mode, the Init
  16316.    routine runs as though it were an application with I/O privilege (IOPL).
  16317.    Consequently (and also unlike all other command code routines) the Init
  16318.    routine can invoke certain file access and internationalization API
  16319.    functions, which are listed in Figure 17-12. These assist the driver in
  16320.    finding and loading font files or other device-dependent information and
  16321.    in displaying error or status messages. When running on an IBM PS/2 or
  16322.    compatible, the Init routine can also call ABIOS services.
  16323.  
  16324. ╓┌─┌──────────────────────────┌──────────────────────────────────────────────╖
  16325.    Function                   Description
  16326.    ──────────────────────────────────────────────────────────────────────────
  16327.    DosBeep                   Generates tone
  16328.    DosCaseMap                Translates ASCII string in place
  16329.    DosChgFilePtr             Moves file read/write pointer
  16330.    DosClose                  Closes file or device
  16331.    DosDelete                 Deletes file
  16332.    DosDevConfig              Returns system hardware configuration
  16333.    DosDevIOCtl               Device-specific commands and information
  16334.    DosFindClose              Releases directory search handle
  16335.    DosFindFirst              Searches for first matching file
  16336.    DosFindNext               Searches for next matching file
  16337.    DosGetCtryInfo            Returns internationalization information
  16338.    DosGetDBCSEv              Returns DBCS environment vector
  16339.    DosGetEnv                 Returns pointer to environment
  16340.    Function                   Description
  16341.    ──────────────────────────────────────────────────────────────────────────
  16342.   DosGetEnv                 Returns pointer to environment
  16343.    DosGetMessage             Returns text for system message
  16344.    DosOpen                   Opens file or device
  16345.    DosPutMessage             Sends message to file or device
  16346.    DosQCurDir                Returns current directory
  16347.    DosQCurDisk               Returns current disk drive
  16348.    DosQFileInfo              Returns file information
  16349.    DosQFileMode              Returns file attributes
  16350.    DosRead                   Reads from file or device
  16351.    DosWrite                  Writes to file or device
  16352.    ──────────────────────────────────────────────────────────────────────────
  16353.  
  16354.  
  16355.    Figure 17-12.  API calls that can be called during driver initialization
  16356.    (command code 0).
  16357.  
  16358.  The Media Check Function
  16359.  
  16360.    The Media Check function (command code 1) is used in block device drivers
  16361.    only. The parameters with which the driver is called and the results that
  16362.    the function returns are summarized in Figure 17-13.
  16363.  
  16364.    The OS/2 kernel calls the Media Check function before it services a drive
  16365.    access call other than a simple file read or write──such as a file open,
  16366.    close, rename, or delete. It passes the medium ID byte (Figure 17-14 on
  16367.    the following page) for the disk that OS/2 assumes is in the drive. The
  16368.    driver returns a code indicating whether the medium has been changed since
  16369.    the last read or write. This change code has one of the following values:
  16370.  
  16371.    Code                      Meaning
  16372.    ──────────────────────────────────────────────────────────────────────────
  16373.    -1                        Medium has been changed
  16374.     0                        Don't know if medium has been changed
  16375.     1                        Medium has not been changed
  16376.    ──────────────────────────────────────────────────────────────────────────
  16377.  
  16378.          Driver called with...                Driver returns...
  16379.    00H  ┌─────────────────────┐       00H  ┌─────────────────────┐
  16380.         │       Length        │            │                     │
  16381.    01H  ├─────────────────────┤       01H  ├─────────────────────┤
  16382.         │        Unit         │            │                     │
  16383.    02H  ├─────────────────────┤       02H  ├─────────────────────┤
  16384.         │    Command code     │            │                     │
  16385.    03H  ├─────────────────────┤       03H  ├─────────────────────┤
  16386.         │                     │            │       Status        │
  16387.    05H  ├─────────────────────┤       05H  ├─────────────────────┤
  16388.         │                     │            │                     │
  16389.         │      Reserved       │            │                     │
  16390.    09H  ├─────────────────────┤       09H  ├─────────────────────┤
  16391.         │                     │            │                     │
  16392.         │    Queue linkage    │            │                     │
  16393.    0DH  ├─────────────────────┤       0DH  ├─────────────────────┤
  16394.         │  Media descriptor   │            │                     │
  16395.    0EH  ├─────────────────────┤       0EH  ├─────────────────────┤
  16396.         │                     │            │     Change code     │
  16397.    0FH  ├─────────────────────┤       0FH  ├─────────────────────┤
  16398.         │                     │            │ Pointer to previous │
  16399.         │                     │            │     volume name     │
  16400.    13H  └─────────────────────┘       13H  └─────────────────────┘
  16401.  
  16402.    Figure 17-13.  Request packet format for the Media Check function (command
  16403.    code 1).
  16404.  
  16405.    If the disk has not been changed, OS/2 accesses the disk without first
  16406.    rereading its FAT. Otherwise, OS/2 calls the driver's Build BPB function
  16407.    (see below) and then reads the disk's FAT and directory.
  16408.  
  16409.    If the Open/Close/RM flag (bit 11) is set in the attribute word of the
  16410.    device driver header, the Media Check function must also return a pointer
  16411.    to the ASCIIZ volume label for the previous disk in the drive (regardless
  16412.    of the change code). If the driver does not have the volume label, it can
  16413.    return a pointer to the ASCIIZ string NO NAME. This allows OS/2 to prompt
  16414.    the user to insert the correct disk if the disk has been changed and the
  16415.    system's buffers hold data that has not been written out.
  16416.  
  16417.    Value                     Disk Type(s)
  16418.    ──────────────────────────────────────────────────────────────────────────
  16419.    0F0H                      3.5", 2-sided, 18 sectors
  16420.    0F8H                      Fixed disk
  16421.    0F9H                      3.5", 2-sided, 9 sectors
  16422.                              5.25", 2-sided, 15 sectors
  16423.    0FCH                      5.25", 1-sided, 9 sectors
  16424.    0FDH                      5.25", 2-sided, 9 sectors
  16425.                              8", 1-sided, single-density
  16426.    0FEH                      5.25", 1-sided, 8 sectors
  16427.                              8", 1-sided, single-density
  16428.                              8", 2-sided, double-density
  16429.    0FFH                      5.25", 2-sided, 8 sectors
  16430.    ──────────────────────────────────────────────────────────────────────────
  16431.  
  16432.    Figure 17-14.  Medium descriptor bytes for IBM-compatible disks. (The
  16433.    table includes 8-inch disk format codes for historical reasons only.)
  16434.  
  16435.  The Build BPB Function
  16436.  
  16437.    The Build BPB function (command code 2) is defined for block devices only.
  16438.    OS/2 calls this function after a Media Check request returns the Medium
  16439.    changed or Don't know codes. The parameters with which the driver is
  16440.    called and the results it returns are summarized in Figure 17-15.
  16441.  
  16442.    The Build BPB routine receives a pointer in the request packet to a
  16443.    1-sector buffer. If bit 13 (the Non-IBM-Format bit) in the attribute word
  16444.    of the device driver header is clear, the buffer contains the first sector
  16445.    of the disk's FAT, with the medium ID byte in the first byte of the
  16446.    buffer. If bit 13 is set, the buffer contains the boot sector from the
  16447.    disk (logical sector 0). The driver should not modify the buffer contents.
  16448.  
  16449.    The Build BPB function must return a status and a pointer to a BIOS
  16450.    parameter block (Figure 17-11 on p. 361) for the disk format indicated by
  16451.    the medium ID byte. The kernel uses the information in the BPB to
  16452.    interpret the disk structure, and the driver itself uses the BPB to
  16453.    translate logical sector addresses into physical track, sector, and head
  16454.    addresses. The Build BPB function should also read the volume label off
  16455.    the disk and save it.
  16456.  
  16457.          Driver called with...            Driver returns...
  16458.    00H  ┌────────────────────┐     00H  ┌────────────────────┐
  16459.         │       Length       │          │                    │
  16460.    01H  ├────────────────────┤     01H  ├────────────────────┤
  16461.         │        Unit        │          │                    │
  16462.    02H  ├────────────────────┤     02H  ├────────────────────┤
  16463.         │    Command code    │          │                    │
  16464.    03H  ├────────────────────┤     03H  ├────────────────────┤
  16465.         │                    │          │       Status       │
  16466.    05H  ├────────────────────┤     05H  ├────────────────────┤
  16467.         │                    │          │                    │
  16468.         │      Reserved      │          │                    │
  16469.    09H  ├────────────────────┤     09H  ├────────────────────┤
  16470.         │                    │          │                    │
  16471.         │   Queue linkage    │          │                    │
  16472.    0DH  ├────────────────────┤     0DH  ├────────────────────┤
  16473.         │ Medium descriptor  │          │                    │
  16474.    0EH  ├────────────────────┤     0EH  ├────────────────────┤
  16475.         │   Pointer to FAT   │          │                    │
  16476.         │       buffer       │          │                    │
  16477.    12H  ├────────────────────┤     12H  ├────────────────────┤
  16478.         │                    │          │                    │
  16479.         │                    │          │   Pointer to BPB   │
  16480.    16H  └────────────────────┘     16H  └────────────────────┘
  16481.  
  16482.    Figure 17-15.  Request packet format for the Build BPB function (command
  16483.    code 2).
  16484.  
  16485.  The Read, Write, and Write with Verify Functions
  16486.  
  16487.    The Read function (command code 4) transfers data from the device to the
  16488.    specified memory address. The Write function (command code 8) transfers
  16489.    data from the specified memory address to the device. The Write with
  16490.    Verify function (command code 9) transfers data from the specified memory
  16491.    address to the device and then verifies that the data was transferred
  16492.    correctly (preferably by reading the data back and comparing it to the
  16493.    original data). The OS/2 kernel calls Write with Verify, instead of Write,
  16494.    whenever the system's global Verify flag is enabled with the VERIFY
  16495.    command or with the API function DosSetVerify. The parameters and results
  16496.    for these command codes are summarized in Figure 17-16 on the following
  16497.    page.
  16498.  
  16499.           Driver called with...                 Driver returns...
  16500.    00H ┌─────────────────────────┐    00H ┌───────────────────────────┐
  16501.        │         Length          │        │                           │
  16502.    01H ╔═════════════════════════╗    01H ├───────────────────────────┤
  16503.        ║          Unit           ║        │                           │
  16504.    02H ╚═════════════════════════╝    02H ├───────────────────────────┤
  16505.        │      Command code       │        │                           │
  16506.    03H ├─────────────────────────┤    03H ├───────────────────────────┤
  16507.        │                         │        │          Status           │
  16508.    05H ├─────────────────────────┤    05H ├───────────────────────────┤
  16509.        │                         │        │                           │
  16510.        │        Reserved         │        │                           │
  16511.    09H ├─────────────────────────┤    09H ├───────────────────────────┤
  16512.        │                         │        │                           │
  16513.        │      Queue linkage      │        │                           │
  16514.    0DH ╔═════════════════════════╗    0DH ├───────────────────────────┤
  16515.        ║    Media descriptor     ║        │                           │
  16516.    0EH ╚═════════════════════════╝    0EH ├───────────────────────────┤
  16517.        │                         │        │                           │
  16518.        │    Transfer address     │        │                           │
  16519.    12H ├─────────────────────────┤    12H ├───────────────────────────┤
  16520.        │ Bytes/sectors requested │        │ Bytes/sectors transferred │
  16521.    14H ╔═════════════════════════╗    14H ├───────────────────────────┤
  16522.        ║                         ║        │                           │
  16523.        ║ Starting sector number  ║        │                           │
  16524.    18H ╚═════════════════════════╝    18H ├───────────────────────────┤
  16525.        │        Reserved         │        │                           │
  16526.    1AH └─────────────────────────┘    1AH └───────────────────────────┘
  16527.  
  16528.    ╔══╗
  16529.    ╚══╝ Block devices only
  16530.  
  16531.    Figure 17-16.  Request packet format for the Read, Write, and Write with
  16532.    Verify functions (command codes 4, 8, and 9). Transfer address is locked
  16533.    and converted to a 32-bit physical address by kernel.
  16534.  
  16535.    The OS/2 kernel calls all three of these driver functions with the memory
  16536.    address and length of the data to be transferred. The memory address is a
  16537.    locked 32-bit physical address; that is, the memory is not swappable or
  16538.    movable, and the segment:offset or selector:offset pair has been converted
  16539.    to a number in the range 00000000H through 00FFFFFFH. (Physical addresses
  16540.    treat the 16 MB of physical RAM as a linear array of bytes.) The driver
  16541.    can use DevHlp functions, which are described later, to convert the
  16542.    physical address into a far pointer appropriate for the current CPU mode.
  16543.    In the case of block device drivers, the kernel also passes the drive unit
  16544.    code, the starting logical sector number, and the medium ID byte for the
  16545.    disk. The driver uses the drive's BPB to translate the logical sector
  16546.    number into a physical track, head, and sector.
  16547.  
  16548.    When the Read, Write, or Write with Verify operation is complete, the
  16549.    functions must return (or the interrupt routine must return on their
  16550.    behalf) a status and the number of bytes or sectors actually transferred.
  16551.    If an I/O error occurs, the Done flag, Error flag, error type, and the
  16552.    number of bytes or sectors successfully transferred prior to the error
  16553.    must be returned to the kernel. The kernel then unlocks the memory.
  16554.  
  16555.  The Nondestructive Read Function
  16556.  
  16557.    The Nondestructive Read function (command code 5) is meaningful in DOS
  16558.    compatibility mode and for character devices only. It returns the next
  16559.    character in the driver's internal buffer, without removing that waiting
  16560.    character from the buffer. Its principal use is to let OS/2 check for a
  16561.    Ctrl-C entered at the keyboard. The parameters and results for this
  16562.    command code are summarized in Figure 17-17.
  16563.  
  16564.    The function returns its result in the Busy bit of the status word. If the
  16565.    driver's input buffer is empty, the function should set the Busy bit. If
  16566.    at least one character is waiting in the input buffer, the function should
  16567.    clear the Busy bit and return the character that would be obtained by a
  16568.    call to the driver's Read function, without removing that character from
  16569.    the buffer.
  16570.  
  16571.          Driver called with...                Driver returns...
  16572.    00H  ┌─────────────────────┐       00H  ┌─────────────────────┐
  16573.         │       Length        │            │                     │
  16574.    01H  ├─────────────────────┤       01H  ├─────────────────────┤
  16575.         │                     │            │                     │
  16576.    02H  ├─────────────────────┤       02H  ├─────────────────────┤
  16577.         │    Command code     │            │                     │
  16578.    03H  ├─────────────────────┤       03H  ├─────────────────────┤
  16579.         │                     │            │       Status        │
  16580.    05H  ├─────────────────────┤       05H  ├─────────────────────┤
  16581.         │                     │            │                     │
  16582.         │      Reserved       │            │                     │
  16583.    09H  ├─────────────────────┤       09H  ├─────────────────────┤
  16584.         │                     │            │                     │
  16585.         │    Queue linkage    │            │                     │
  16586.    0DH  ├─────────────────────┤       0DH  ├─────────────────────┤
  16587.         │                     │            │      Character      │
  16588.    0EH  └─────────────────────┘       0EH  └─────────────────────┘
  16589.  
  16590.    Figure 17-17.  Request packet format for Nondestructive Read (command code
  16591.    5).
  16592.  
  16593.  The Input Status and Output Status Functions
  16594.  
  16595.    The Input Status and Output Status (command codes 6 and 10) functions are
  16596.    defined only for character devices. Both return their results in the Busy
  16597.    bit of the status word. The parameters and results for these command codes
  16598.    are summarized in Figure 17-18.
  16599.  
  16600.    OS/2 calls the Input Status function to determine whether characters are
  16601.    waiting, buffered within the driver. The function clears the Busy bit if
  16602.    at least one character is already in the driver's input buffer. It sets
  16603.    the Busy bit if no characters are in the buffer, in which event a Read
  16604.    request for one character would not complete immediately. If the driver
  16605.    does not buffer characters internally, the Input Status function should
  16606.    always clear the Busy bit so that OS/2 will not wait for a character
  16607.    arrive in the buffer before issuing a Read request.
  16608.  
  16609.    OS/2 uses the Output Status function to determine whether a write
  16610.    operation is already in progress for the device. The function clears the
  16611.    Busy bit if the device is idle and if a Write request would start
  16612.    immediately, or it sets the Busy bit if a write is already in progress.
  16613.  
  16614.          Driver called with...                Driver returns...
  16615.    00H  ┌─────────────────────┐       00H  ┌─────────────────────┐
  16616.         │       Length        │            │                     │
  16617.    01H  ├─────────────────────┤       01H  ├─────────────────────┤
  16618.         │                     │            │                     │
  16619.    02H  ├─────────────────────┤       02H  ├─────────────────────┤
  16620.         │    Command code     │            │                     │
  16621.    03H  ├─────────────────────┤       03H  ├─────────────────────┤
  16622.         │                     │            │       Status        │
  16623.    05H  ├─────────────────────┤       05H  ├─────────────────────┤
  16624.         │                     │            │                     │
  16625.         │      Reserved       │            │                     │
  16626.    09H  ├─────────────────────┤       09H  ├─────────────────────┤
  16627.         │                     │            │                     │
  16628.         │    Queue linkage    │            │                     │
  16629.    0DH  └─────────────────────┘       0DH  └─────────────────────┘
  16630.  
  16631.    Figure 17-18.  Request packet format for the Input Status (command code
  16632.    6), Flush Input Buffers (command code 7), Output Status (command code 10),
  16633.    and Flush Output Buffers (command code 11) functions.
  16634.  
  16635.  The Flush Input Buffer and Flush Output Buffer Functions
  16636.  
  16637.    The Flush Input Buffer (command code 7) and Flush Output Buffer (command
  16638.    code 11) functions are supported only for character device drivers. They
  16639.    simply terminate any read (for Flush Input) or write (for Flush Output)
  16640.    operations that are in progress, discard any requests that are pending on
  16641.    the driver's work list, and empty the associated buffer. The parameters
  16642.    and results for these command codes are summarized in Figure 17-18.
  16643.  
  16644.    These functions constitute the driver level support for DosDevIOCtl
  16645.    Category 11 Functions 01H (Flush Input Buffer) and 02H (Flush Output
  16646.    Buffer).
  16647.  
  16648.  The Device Open and Device Close Functions
  16649.  
  16650.    The Device Open and Device Close (command codes 13 and 14) functions are
  16651.    called only if the Open/Close/Removable Media flag (bit 11) is set in the
  16652.    attribute word of the device driver header. If a Device Open or Device
  16653.    Close request is directed to a block device driver, the request packet
  16654.    includes a unit code. The parameters and results for these command codes
  16655.    are summarized in Figure 17-19.
  16656.  
  16657.          Driver called with...              Driver returns...
  16658.    00H  ┌─────────────────────┐     00H  ┌─────────────────────┐
  16659.         │       Length        │          │                     │
  16660.    01H  ╔═════════════════════╗     01H  ├─────────────────────┤
  16661.         ║        Unit         ║          │                     │
  16662.    02H  ╚═════════════════════╝     02H  ├─────────────────────┤
  16663.         │    Command code     │          │                     │
  16664.    03H  ├─────────────────────┤     03H  ├─────────────────────┤
  16665.         │                     │          │       Status        │
  16666.    05H  ├─────────────────────┤     05H  ├─────────────────────┤
  16667.         │                     │          │                     │
  16668.         │      Reserved       │          │                     │
  16669.    09H  ├─────────────────────┤     09H  ├─────────────────────┤
  16670.         │                     │          │                     │
  16671.         │    Queue linkage    │          │                     │
  16672.    0DH  ├─────────────────────┤     0DH  ├─────────────────────┤
  16673.         │      Reserved       │          │                     │
  16674.    0FH  └─────────────────────┘     0FH  └─────────────────────┘
  16675.  
  16676.    ╔══╗ Block devices only
  16677.    ╚══╝
  16678.  
  16679.    Figure 17-19.  Request packet format for the Device Open (command code 13)
  16680.    and Device Close (command code 14) functions.
  16681.  
  16682.    Each application call to DosOpen to open or create a file or to open a
  16683.    character device for input or output results in a Device Open request from
  16684.    the kernel to the corresponding device driver. Similarly, each DosClose
  16685.    call by an application to close a file or device results in a Device Close
  16686.    call by the kernel to the appropriate driver.
  16687.  
  16688.    On block devices, you can use the Device Open and Device Close functions
  16689.    to manage local buffering and to maintain a reference count of the number
  16690.    of open files on a device. Whenever this reference count is decremented to
  16691.    zero, indicating that all files on the disk have been closed, the driver
  16692.    should flush any internal buffers so that no data is lost if the user
  16693.    proceeds to change disks.
  16694.  
  16695.    Character device drivers can respond to the Device Open and Device Close
  16696.    by sending hardware-dependent initialization and post-I/O strings to the
  16697.    associated device (for example, a reset sequence or formfeed character to
  16698.    precede new output, and a formfeed to follow it). If the driver is
  16699.    appropriately designed, an application can inspect or change the strings
  16700.    with DosDevIOCtl calls.
  16701.  
  16702.    When an application issues a call to DosMonOpen or DosMonClose, the driver
  16703.    receives a special Device Open or Device Close request packet with bit 3
  16704.    of the status word set. These functions assist the driver in maintaining
  16705.    its device monitor chain, as described in more detail in Chapter 18.
  16706.  
  16707.  The Removable Media Function
  16708.  
  16709.    The Removable Media function (command code 15) is defined for block
  16710.    devices only. OS/2 does not call this function unless the Open/Close/
  16711.    Removable Media flag (bit 11) is set in the attribute word of the device
  16712.    driver header. This function constitutes the driver level support for the
  16713.    service that OS/2 provides to application programs with DosDevIOCtl
  16714.    Category 8 Function 20H (Check if Block Device Removable). The parameters
  16715.    and results for this command code are summarized in Figure 17-20.
  16716.  
  16717.          Driver called with...              Driver returns...
  16718.    00H  ┌─────────────────────┐     00H  ┌─────────────────────┐
  16719.         │       Length        │          │                     │
  16720.    01H  ╔═════════════════════╗     01H  ├─────────────────────┤
  16721.         ║        Unit         ║          │                     │
  16722.    02H  ╚═════════════════════╝     02H  ├─────────────────────┤
  16723.         │    Command code     │          │                     │
  16724.    03H  ├─────────────────────┤     03H  ├─────────────────────┤
  16725.         │                     │          │       Status        │
  16726.    05H  ├─────────────────────┤     05H  ├─────────────────────┤
  16727.         │                     │          │                     │
  16728.         │      Reserved       │          │                     │
  16729.    09H  ├─────────────────────┤     09H  ├─────────────────────┤
  16730.         │                     │          │                     │
  16731.         │    Queue linkage    │          │                     │
  16732.    0DH  └─────────────────────┘     0DH  └─────────────────────┘
  16733.    ╔══╗ Block devices only
  16734.    ╚══╝
  16735.  
  16736.    Figure 17-20.  Request packet format for the Removable Media (command code
  16737.    15), Reset Media (command code 17), and DeInstall Driver (command code 20)
  16738.    functions.
  16739.  
  16740.    The only parameter for the Removable Media function is the unit code. The
  16741.    function returns its result in the Busy bit of the status word. The Busy
  16742.    bit is set if the disk is fixed; it is cleared if the disk is removable.
  16743.  
  16744.  The Generic IOCtl Function
  16745.  
  16746.    The Generic IOCtl function (command code 16) corresponds to the OS/2 API
  16747.    function DosDevIOCtl; it provides a general-purpose high performance
  16748.    channel of direct communication between applications and device drivers.
  16749.    The parameters and results for this command code are summarized in Figure
  16750.    17-21 on the following page.
  16751.  
  16752.    In addition to the usual information in the static portion of the request
  16753.    packet, the Generic IOCtl function is passed a category (major) code, a
  16754.    function (minor) code, and pointers to a parameter block and a data
  16755.    buffer. The pointers are virtual addresses; the driver is responsible for
  16756.    ensuring that the application is actually authorized to use those virtual
  16757.    addresses. If the request cannot be completed at task time, your driver
  16758.    must ensure later addressability of the parameter block and data buffer by
  16759.    accomplishing the following sequence of steps:
  16760.  
  16761.    1.  Lock the virtual addresses so that the corresponding memory segments
  16762.        will not be moved or swapped by the system's memory manager.
  16763.  
  16764.    2.  Replace the virtual addresses in the request packet with the
  16765.        corresponding physical addresses.
  16766.  
  16767.    3.  When the driver is able to service the request, translate the physical
  16768.        addresses back to virtual addresses that are appropriate for the
  16769.        current mode.
  16770.  
  16771.    4.  When the request has been serviced, unlock the segments.
  16772.  
  16773.    The driver must interpret the category and function codes in the request
  16774.    packet and the contents of the parameter block or data buffer (or both) to
  16775.    determine which operation it will carry out; subsequently, it sets the
  16776.    status word appropriately and returns any other pertinent information in
  16777.    the application's data buffer.
  16778.  
  16779.    Services that can be invoked by the Generic IOCtl function, if the driver
  16780.    supports them, include configuring a serial port, selecting nonstandard
  16781.    disk formats, reading and writing entire tracks, and formatting and
  16782.    verifying tracks. The Generic IOCtl function is designed to allow easy
  16783.    communication between closely coupled applications and custom device
  16784.    drivers, and is open-ended so that it can be used to extend the device
  16785.    driver definition in future versions of OS/2.
  16786.  
  16787.           Driver called with...               Driver returns...
  16788.    00H  ┌────────────────────────┐    00H  ┌─────────────────────┐
  16789.         │         Length         │         │                     │
  16790.    01H  ╔════════════════════════╗    01H  ├─────────────────────┤
  16791.         ║          Unit          ║         │                     │
  16792.    02H  ╚════════════════════════╝    02H  ├─────────────────────┤
  16793.         │      Command code      │         │                     │
  16794.    03H  ├────────────────────────┤    03H  ├─────────────────────┤
  16795.         │                        │         │       Status        │
  16796.    05H  ├────────────────────────┤    05H  ├─────────────────────┤
  16797.         │                        │         │                     │
  16798.         │        Reserved        │         │                     │
  16799.    09H  ├────────────────────────┤    09H  ├─────────────────────┤
  16800.         │                        │         │                     │
  16801.         │     Queue linkage      │         │                     │
  16802.    0DH  ├────────────────────────┤    0DH  ├─────────────────────┤
  16803.         │     Category code      │         │                     │
  16804.    0EH  ├────────────────────────┤    0EH  ├─────────────────────┤
  16805.         │     Function code      │         │                     │
  16806.    OFH  ├────────────────────────┤    OFH  ├─────────────────────┤
  16807.         │       Pointer to       │         │                     │
  16808.         │    parameter block     │         │                     │
  16809.    13H  ├────────────────────────┤    13H  ├─────────────────────┤
  16810.         │       Pointer to       │         │                     │
  16811.         │       data buffer      │         │                     │
  16812.    17H  ├────────────────────────┤    17H  ├─────────────────────┤
  16813.         │        Reserved        │         │                     │
  16814.    19H  └────────────────────────┘    19H  └─────────────────────┘
  16815.  
  16816.    ╔══╗ Block devices only
  16817.    ╚══╝
  16818.  
  16819.    Figure 17-21.  Request packet format for the Generic IOCtl function
  16820.    (command code 16). Both pointers are virtual addresses, with selectors
  16821.    from the current process's LDT. The process's right to use the addresses
  16822.    must be confirmed with the DevHlp function VerifyAccess, and the virtual
  16823.    addresses must be locked and converted to physical addresses if request
  16824.    cannot be disposed of at task time.
  16825.  
  16826.  The Reset Media Function
  16827.  
  16828.    The Reset Media function (command code 17) is defined only for block
  16829.    devices. The function is called with no parameters in the request packet
  16830.    other than the command and unit codes, and it returns only a status to the
  16831.    kernel. The parameters and results for this command code are summarized in
  16832.    Figure 17-20 on p. 370.
  16833.  
  16834.    When the driver returns an Uncertain medium error to a service request,
  16835.    the kernel calls Reset Media to inform the driver that it no longer needs
  16836.    to return that error for the drive.
  16837.  
  16838.  The Get Logical Drive and Set Logical Drive Functions
  16839.  
  16840.    The Get Logical Drive and Set Logical Drive functions (command codes 18
  16841.    and 19) are defined for block devices only. They correspond to the API
  16842.    services supplied by OS/2 to application programs via DosDevIOCtl Category
  16843.    8, Functions 03H (Set Logical Map) and 21H (Get Logical Map). Both
  16844.    functions are called with a logical unit code in the request packet and
  16845.    must return a status and a logical unit. The parameters and results for
  16846.    these command codes are summarized in Figure 17-22.
  16847.  
  16848.    The Get Logical Drive function is called to determine whether more than
  16849.    one logical unit code is assigned to the same physical device. It returns
  16850.    a code for the last drive letter used to reference the device (1 = A, 2 =
  16851.    B, and so on); if only one drive letter is assigned to the device, the
  16852.    returned unit code should be zero.
  16853.  
  16854.    The Set Logical Device function is called to inform the driver of the next
  16855.    logical drive code that will be used to reference the device. The unit
  16856.    code passed by the OS/2 kernel is zero-based relative to the logical
  16857.    drives supported by this particular driver. The driver performs the
  16858.    requested mapping and returns the logical unit unchanged.
  16859.  
  16860.    For example, if the driver supports two logical floppy disk units (A and
  16861.    B), and only one physical floppy disk drive exists in the system, then a
  16862.    call to Set Logical Device with a unit number of 1 informs the driver that
  16863.    the next read or write request from OS/2 will be directed to logical drive
  16864.    B.
  16865.  
  16866.          Driver called with...              Driver returns...
  16867.    00H  ┌─────────────────────┐     00H  ┌─────────────────────┐
  16868.         │       Length        │          │                     │
  16869.    01H  ├─────────────────────┤     01H  ├─────────────────────┤
  16870.         │        Unit         │          │        Unit         │
  16871.    02H  ├─────────────────────┤     02H  ├─────────────────────┤
  16872.         │    Command code     │          │                     │
  16873.    03H  ├─────────────────────┤     03H  ├─────────────────────┤
  16874.         │                     │          │       Status        │
  16875.    05H  ├─────────────────────┤     05H  ├─────────────────────┤
  16876.         │                     │          │                     │
  16877.         │      Reserved       │          │                     │
  16878.    09H  ├─────────────────────┤     09H  ├─────────────────────┤
  16879.         │                     │          │                     │
  16880.         │    Queue linkage    │          │                     │
  16881.    0DH  └─────────────────────┘     0DH  └─────────────────────┘
  16882.  
  16883.    Figure 17-22.  Request packet format for the Get Logical Drive and Set
  16884.    Logical Drive functions (command codes 18 and 19).
  16885.  
  16886.  The DeInstall Driver Function
  16887.  
  16888.    The DeInstall Driver function (command code 20) is valid for character
  16889.    devices only. It is called when the OS/2 kernel loads another character
  16890.    device driver with the same logical name as the driver that receives the
  16891.    DeInstall Driver request packet. The parameters and results for this
  16892.    command code are summarized in Figure 17-20 on p. 370.
  16893.  
  16894.    If the current driver refuses the DeInstall Driver command, it should
  16895.    return to the kernel with the status word set to 8103H (Error bit, Done
  16896.    bit, and error code unknown command). In this event, the system does not
  16897.    load the replacement driver.
  16898.  
  16899.    If the current driver accepts the DeInstall Driver command, allowing
  16900.    itself to be superseded, it must perform the following actions:
  16901.  
  16902.    ■  Call the DevHlp function FreePhys to release any memory it has
  16903.       previously allocated with AllocPhys
  16904.  
  16905.    ■  Call the DevHlp function UnSetIRQ to release any interrupts it has
  16906.       previously registered with SetIRQ
  16907.  
  16908.    ■  Relinquish any device logical IDs it has previously acquired by calling
  16909.       the DevHlp function FreeLIDEntry (if the driver runs on an IBM PS/2 and
  16910.       uses the ABIOS)
  16911.  
  16912.    ■  Perform any other necessary cleanup of system resources, such as
  16913.       semaphores
  16914.  
  16915.    ■  Set the Done flag in the status word of the request packet
  16916.  
  16917.    After the driver accepting the DeInstall Driver command returns to the
  16918.    kernel, its code and memory segments are released. If the physical device
  16919.    supported by the driver cannot be told to stop generating interrupts, then
  16920.    the driver must refuse the DeInstall Driver operation rather than risk
  16921.    leaving the system without an interrupt handler.
  16922.  
  16923.  The Partitionable Fixed Disks Function
  16924.  
  16925.    The Partitionable Fixed Disks function (command code 22) is defined for
  16926.    block device drivers only. The function returns a status along with the
  16927.    number of physical, partitionable fixed disks that the driver supports.
  16928.    The parameters and results for this command code are shown in Figure
  16929.    17-23.
  16930.  
  16931.    The information returned by this function allows the kernel to route the
  16932.    DosDevIOCtl Category 9 (Physical Disk Control) requests to the appropriate
  16933.    block device driver.
  16934.  
  16935.          Driver called with...              Driver returns...
  16936.    00H  ┌─────────────────────┐     00H  ┌─────────────────────┐
  16937.         │       Length        │          │                     │
  16938.    01H  ├─────────────────────┤     01H  ├─────────────────────┤
  16939.         │        Unit         │          │                     │
  16940.    02H  ├─────────────────────┤     02H  ├─────────────────────┤
  16941.         │    Command code     │          │                     │
  16942.    03H  ├─────────────────────┤     03H  ├─────────────────────┤
  16943.         │                     │          │       Status        │
  16944.    05H  ├─────────────────────┤     05H  ├─────────────────────┤
  16945.         │                     │          │                     │
  16946.         │      Reserved       │          │                     │
  16947.    09H  ├─────────────────────┤     09H  ├─────────────────────┤
  16948.         │                     │          │                     │
  16949.         │    Queue linkage    │          │                     │
  16950.    0DH  ├─────────────────────┤     0DH  ├─────────────────────┤
  16951.         │                     │          │        Count        │
  16952.    0EH  ├─────────────────────┤     0EH  ├─────────────────────┤
  16953.         │      Reserved       │          │                     │
  16954.    10H  ├─────────────────────┤     10H  ├─────────────────────┤
  16955.         │      Reserved       │          │                     │
  16956.    12H  └─────────────────────┘     12H  └─────────────────────┘
  16957.  
  16958.    Figure 17-23.  Request packet format for the Partitionable Fixed Disks
  16959.    function (command code 22).
  16960.  
  16961.  The Get Fixed Disk/Logical Unit Map Function
  16962.  
  16963.    The Get Fixed Disk/Logical Unit Map function (command code 23) is defined
  16964.    for block device drivers only. The function is called with the unit number
  16965.    for a physical fixed disk in the request header; this number is calculated
  16966.    on the basis of previous calls to Partitionable Fixed Disks (command code
  16967.    22). The parameters and results for this command code are summarized in
  16968.    Figure 17-24 on the following page.
  16969.  
  16970.    The device driver is responsible for returning a status and a 4-byte
  16971.    "units-supported bit mask" that describes which of its own logical units,
  16972.    numbered from zero, exist on the specified physical drive. The bits in the
  16973.    mask are assigned sequentially from the least significant bit of the first
  16974.    byte (the byte at the lowest address) to the most significant bit of the
  16975.    last byte (the byte at the highest address).
  16976.  
  16977.          Driver called with...              Driver returns...
  16978.    00H  ┌─────────────────────┐     00H  ┌─────────────────────┐
  16979.         │       Length        │          │                     │
  16980.    01H  ├─────────────────────┤     01H  ├─────────────────────┤
  16981.         │        Unit         │          │                     │
  16982.    02H  ├─────────────────────┤     02H  ├─────────────────────┤
  16983.         │    Command code     │          │                     │
  16984.    03H  ├─────────────────────┤     03H  ├─────────────────────┤
  16985.         │                     │          │       Status        │
  16986.    05H  ├─────────────────────┤     05H  ├─────────────────────┤
  16987.         │                     │          │                     │
  16988.         │      Reserved       │          │                     │
  16989.    09H  ├─────────────────────┤     09H  ├─────────────────────┤
  16990.         │                     │          │                     │
  16991.         │    Queue linkage    │          │                     │
  16992.    0DH  ├─────────────────────┤     0DH  ├─────────────────────┤
  16993.         │                     │          │   Units-supported   │
  16994.         │                     │          │      bit mask       │
  16995.    11H  ├─────────────────────┤     11H  ├─────────────────────┤
  16996.         │      Reserved       │          │                     │
  16997.    13H  ├─────────────────────┤     13H  ├─────────────────────┤
  16998.         │      Reserved       │          │                     │
  16999.    15H  └─────────────────────┘     15H  └─────────────────────┘
  17000.  
  17001.    Figure 17-24.  Request packet format for the Get Fixed Disk/Logical Unit
  17002.    Map function (command code 23).
  17003.  
  17004.  
  17005.  A Skeleton Device Driver
  17006.  
  17007.    The listing TEMPLATE.ASM (Figure 17-25) provides the source code for a
  17008.    skeleton character device driver called TEMPLATE.SYS. Its module
  17009.    definition file is TEMPLATE.DEF (Figure 17-26 on p. 383). This driver
  17010.    contains all the essential elements of an OS/2 device driver in
  17011.    rudimentary form: the device header, strategy routine, interrupt routine,
  17012.    and all the command code functions. It also demonstrates the preferred
  17013.    segment ordering and naming conventions, the use of API calls at
  17014.    initialization time, and the method by which initialization code and data
  17015.    can be discarded to minimize a driver's memory requirements.
  17016.  
  17017.    TEMPLATE.SYS performs no useful function in its present form; it simply
  17018.    returns a success code for all request packets it receives from the
  17019.    kernel. It is intended only to serve as a starting point for your own
  17020.    device drivers.
  17021.  
  17022.    ──────────────────────────────────────────────────────────────────────────
  17023.            title   TEMPLATE -- Sample Device Driver
  17024.            page    55,132
  17025.            .286
  17026.  
  17027.    ;
  17028.    ; TEMPLATE.ASM
  17029.    ;
  17030.    ; A sample OS/2 character device driver. The driver command code
  17031.    ; routines are stubs only and have no effect but to return a
  17032.    ; nonerror "done" status.
  17033.    ;
  17034.    ; Assemble with:  C> masm template.asm;
  17035.    ; Link with:  C> link template,template.sys,,os2,template
  17036.    ;
  17037.    ; To install the driver, add "DEVICE=TEMPLATE.SYS" to CONFIG.SYS
  17038.    ; and reboot.
  17039.    ;
  17040.    ; Copyright (C) 1988 Ray Duncan
  17041.    ;
  17042.  
  17043.    maxcmd  equ     26              ; maximum allowed command code
  17044.  
  17045.    stdin   equ     0               ; standard device handles
  17046.    stdout  equ     1
  17047.    stderr  equ     2
  17048.  
  17049.    cr      equ     0dh             ; ASCII carriage return
  17050.    lf      equ     0ah             ; ASCII linefeed
  17051.  
  17052.            extrn   DosWrite:far
  17053.  
  17054.  
  17055.    DGROUP  group   _DATA
  17056.  
  17057.    _DATA   segment word public 'DATA'
  17058.  
  17059.                                    ; device driver header...
  17060.    header  dd      -1              ; link to next device driver
  17061.            dw      8880h           ; device attribute word
  17062.            dw      Strat           ; Strategy entry point
  17063.            dw      0               ; IDC entry point
  17064.            db      'TEMPLATE'      ; logical device name
  17065.            db      8 dup (0)       ; reserved
  17066.  
  17067.    devhlp  dd      ?               ; DevHlp entry point
  17068.    wlen    dw      ?               ; receives DosWrite length
  17069.  
  17070.                                    ; Strategy routine dispatch table
  17071.                                    ; for request packet command code...
  17072.    dispch  dw      Init            ; 0  = initialize driver
  17073.            dw      MediaChk        ; 1  = media check
  17074.            dw      BuildBPB        ; 2  = build BIOS parameter block
  17075.            dw      Error           ; 3  = not used
  17076.            dw      Read            ; 4  = read from device
  17077.            dw      NdRead          ; 5  = nondestructive read
  17078.            dw      InpStat         ; 6  = return input status
  17079.            dw      InpFlush        ; 7  = flush device input buffers
  17080.            dw      Write           ; 8  = write to device
  17081.            dw      WriteVfy        ; 9  = write with verify
  17082.            dw      OutStat         ; 10 = return output status
  17083.            dw      OutFlush        ; 11 = flush output buffers
  17084.            dw      Error           ; 12 = not used
  17085.            dw      DevOpen         ; 13 = device open
  17086.            dw      DevClose        ; 14 = device close
  17087.            dw      RemMedia        ; 15 = removable media
  17088.            dw      GenIOCTL        ; 16 = generic IOCTL
  17089.            dw      ResetMed        ; 17 = reset media
  17090.            dw      GetLogDrv       ; 18 = get logical drive
  17091.            dw      SetLogDrv       ; 19 = set logical drive
  17092.            dw      DeInstall       ; 20 = deinstall
  17093.            dw      Error           ; 21 = not used
  17094.            dw      PartFD          ; 22 = partitionable fixed disks
  17095.            dw      FDMap           ; 23 = get fixed disk unit map
  17096.            dw      Error           ; 24 = not used
  17097.            dw      Error           ; 25 = not used
  17098.            dw      Error           ; 26 = not used
  17099.  
  17100.    ident   db      cr,lf,lf
  17101.            db      'TEMPLATE Sample OS/2 Device Driver'
  17102.            db      cr,lf
  17103.    ident_len equ $-ident
  17104.  
  17105.    _DATA   ends
  17106.  
  17107.  
  17108.    _TEXT   segment word public 'CODE'
  17109.  
  17110.            assume  cs:_TEXT,ds:DGROUP,es:NOTHING
  17111.  
  17112.    Strat   proc    far             ; Strategy entry point
  17113.                                    ; ES:BX = request packet address
  17114.            mov     di,es:[bx+2]    ; get command code from packet
  17115.            and     di,0ffh
  17116.            cmp     di,maxcmd       ; supported by this driver?
  17117.            jle     Strat1          ; jump if command code OK
  17118.  
  17119.            call    Error           ; bad command code
  17120.            jmp     Strat2
  17121.  
  17122.    Strat1: add     di,di           ; branch to command code routine
  17123.            call    word ptr [di+dispch]
  17124.  
  17125.    Strat2: mov     es:[bx+3],ax    ; status into request packet
  17126.            ret                     ; back to OS/2 kernel
  17127.  
  17128.    Strat   endp
  17129.  
  17130.  
  17131.    Intr    proc  far               ; driver Interrupt handler
  17132.  
  17133.            clc                     ; signal we owned interrupt
  17134.            ret                     ; return from interrupt
  17135.  
  17136.    Intr    endp
  17137.  
  17138.  
  17139.    ; Command code routines are called by the Strategy routine
  17140.    ; via the Dispatch table with ES:BX pointing to the request
  17141.    ; header.  Each routine should return ES:BX unchanged
  17142.    ; and AX = status to be placed in request packet:
  17143.    ; 0100H if 'done' and no error
  17144.    ; 0000H if thread should block pending interrupt
  17145.    ; 81xxH if 'done' and error detected (xx=error code)
  17146.  
  17147.    MediaChk proc   near            ; function 1 = media check
  17148.  
  17149.            mov     ax,0100h        ; return 'done' status
  17150.            ret
  17151.  
  17152.    MediaChk endp
  17153.  
  17154.  
  17155.    BuildBPB proc   near            ; function 2 = build BPB
  17156.  
  17157.            mov     ax,0100h        ; return 'done' status
  17158.            ret
  17159.  
  17160.    BuildBPB endp
  17161.    Read    proc    near            ; function 4 = read
  17162.  
  17163.            mov     ax,0100h        ; return 'done' status
  17164.            ret
  17165.  
  17166.    Read    endp
  17167.  
  17168.  
  17169.    NdRead  proc    near            ; function 5 = nondestructive read
  17170.  
  17171.            mov     ax,0100h        ; return 'done' status
  17172.            ret
  17173.  
  17174.    NdRead  endp
  17175.  
  17176.  
  17177.    InpStat proc    near            ; function 6 = input status
  17178.  
  17179.            mov     ax,0100h        ; return 'done' status
  17180.            ret
  17181.  
  17182.    InpStat endp
  17183.  
  17184.  
  17185.    InpFlush proc   near            ; function 7 = flush input buffers
  17186.  
  17187.            mov     ax,0100h        ; return 'done' status
  17188.            ret
  17189.  
  17190.    InpFlush endp
  17191.  
  17192.  
  17193.    Write   proc    near            ; function 8 = write
  17194.  
  17195.            mov     ax,0100h        ; return 'done' status
  17196.            ret
  17197.  
  17198.    Write   endp
  17199.  
  17200.  
  17201.    WriteVfy proc   near            ; function 9 = write with verify
  17202.  
  17203.            mov     ax,0100h        ; return 'done' status
  17204.            ret
  17205.  
  17206.    WriteVfy endp
  17207.    OutStat proc    near            ; function 10 = output status
  17208.  
  17209.            mov     ax,0100h        ; return 'done' status
  17210.            ret
  17211.  
  17212.    OutStat endp
  17213.  
  17214.  
  17215.    OutFlush proc   near            ; function 11 = flush output buffers
  17216.  
  17217.            mov     ax,0100h        ; return 'done' status
  17218.            ret
  17219.  
  17220.    OutFlush endp
  17221.  
  17222.  
  17223.    DevOpen proc    near            ; function 13 = device open
  17224.  
  17225.            mov     ax,0100h        ; return 'done' status
  17226.            ret
  17227.  
  17228.    DevOpen endp
  17229.  
  17230.  
  17231.    DevClose proc   near            ; function 14 = device close
  17232.  
  17233.            mov     ax,0100h        ; return 'done' status
  17234.            ret
  17235.  
  17236.    DevClose endp
  17237.  
  17238.  
  17239.    RemMedia proc   near            ; function 15 = removable media
  17240.  
  17241.            mov     ax,0100h        ; return 'done' status
  17242.            ret
  17243.  
  17244.    RemMedia endp
  17245.  
  17246.  
  17247.    GenIOCTL proc   near            ; function 16 = generic IOCTL
  17248.  
  17249.            mov     ax,0100h        ; return 'done' status
  17250.            ret
  17251.  
  17252.    GenIOCTL endp
  17253.    ResetMed proc   near            ; function 17 = reset media
  17254.  
  17255.            mov     ax,0100h        ; return 'done' status
  17256.            ret
  17257.  
  17258.    ResetMed endp
  17259.  
  17260.  
  17261.    GetLogDrv proc  near            ; function 18 = get logical drive
  17262.  
  17263.            mov     ax,0100h        ; return 'done' status
  17264.            ret
  17265.  
  17266.    GetLogDrv endp
  17267.  
  17268.  
  17269.    SetLogDrv proc  near            ; function 19 = set logical drive
  17270.  
  17271.            mov     ax,0100h        ; return 'done' status
  17272.            ret
  17273.  
  17274.    SetLogDrv endp
  17275.  
  17276.  
  17277.    DeInstall proc  near            ; function 20 = deinstall driver
  17278.  
  17279.            mov     ax,0100h        ; return 'done' status
  17280.            ret
  17281.  
  17282.    DeInstall endp
  17283.  
  17284.  
  17285.    PartFD  proc    near            ; function 22 = partitionable
  17286.                                    ;               fixed disk
  17287.            mov     ax,0100h        ; return 'done' status
  17288.            ret
  17289.  
  17290.    PartFD  endp
  17291.  
  17292.  
  17293.    FDMap   proc    near            ; function 23 = get fixed disk
  17294.                                    ;               logical unit map
  17295.            mov     ax,0100h        ; return 'done' status
  17296.            ret
  17297.  
  17298.    FDMap   endp
  17299.    Error   proc    near            ; bad command code
  17300.  
  17301.            mov     ax,8103h        ; error bit and 'done' status
  17302.                                    ; + "Unknown Command" code
  17303.            ret
  17304.  
  17305.    Error   endp
  17306.  
  17307.  
  17308.    Init    proc    near            ; function 0 = initialize
  17309.  
  17310.            mov     ax,es:[bx+14]   ; get DevHlp entry point
  17311.            mov     word ptr devhlp,ax
  17312.            mov     ax,es:[bx+16]
  17313.            mov     word ptr devhlp+2,ax
  17314.  
  17315.                                    ; set offsets to end of code
  17316.                                    ; and data segments
  17317.            mov     word ptr es:[bx+14],offset _TEXT:Init
  17318.            mov     word ptr es:[bx+16],offset DGROUP:ident
  17319.  
  17320.                                    ; display sign-on message...
  17321.            push    stdout          ; standard output handle
  17322.            push    ds              ; address of message
  17323.            push    offset DGROUP:ident
  17324.            push    ident_len       ; length of message
  17325.            push    ds              ; receives bytes written
  17326.            push    offset DGROUP:wlen
  17327.            call    DosWrite        ; transfer to OS/2
  17328.  
  17329.            mov     ax,0100h        ; return 'done' status
  17330.            ret
  17331.  
  17332.    Init    endp
  17333.  
  17334.    _TEXT   ends
  17335.  
  17336.            end
  17337.    ──────────────────────────────────────────────────────────────────────────
  17338.  
  17339.    Figure 17-25.  TEMPLATE.ASM, the source code for a skeleton OS/2 character
  17340.    device driver.
  17341.  
  17342.    ──────────────────────────────────────────────────────────────────────────
  17343.    LIBRARY TEMPLATE
  17344.    PROTMODE
  17345.    ──────────────────────────────────────────────────────────────────────────
  17346.  
  17347.    Figure 17-26.  TEMPLATE.DEF, the module definition file used during
  17348.    linking of TEMPLATE.SYS.
  17349.  
  17350.    To assemble TEMPLATE.ASM, type the following command:
  17351.  
  17352.    [C:\] MASM TEMPLATE.ASM;  <Enter>
  17353.  
  17354.    Then link TEMPLATE.OBJ with TEMPLATE.DEF and OS2.LIB using the command:
  17355.  
  17356.    [C:\] LINK TEMPLATE,TEMPLATE.SYS,,OS2,TEMPLATE  <Enter>
  17357.  
  17358.    If the assembly and link are successful, you can then copy the new file
  17359.    TEMPLATE.SYS to the root directory of your boot disk, add the line
  17360.    DEVICE=TEMPLATE.SYS to the CONFIG.SYS file, and restart the system to see
  17361.    the sign-on message for the TEMPLATE driver:
  17362.  
  17363.    TEMPLATE Sample OS/2 Device Driver
  17364.  
  17365.  
  17366.  The Device Driver Helper Functions
  17367.  
  17368.    The Device Driver Helper functions, or DevHlps, are kernel services that
  17369.    assist device drivers in carrying out certain critical or complex tasks.
  17370.    Moving functionality that all drivers need into the kernel ensures that
  17371.    these activities are carried out in a uniform and efficient way. It also
  17372.    allows each driver to be smaller, simpler, and easier to debug.
  17373.  
  17374.    Unlike the kernel Application Program Interface (API), for which
  17375.    parameters are passed on the stack and for which each function has a
  17376.    distinct named entry point, the DevHlp interface uses a register-based
  17377.    parameter-passing convention and a common entry point. The driver is
  17378.    provided with a bimodal pointer to the DevHlp entry point in the request
  17379.    packet it receives from the kernel during its initialization.
  17380.  
  17381.    When a driver calls the DevHlp entry point, it selects a particular DevHlp
  17382.    function by the value it places in register DL. It passes additional
  17383.    parameters and addresses in other general registers as demanded by the
  17384.    requested function. The status of a DevHlp function is usually returned to
  17385.    the driver in the Zero flag or Carry flag, error codes in register AX, and
  17386.    addresses in registers DS:SI or ES:DI. Figure 17-27 shows a typical
  17387.    DevHlp call.
  17388.  
  17389.    The fifty-odd DevHlp functions can be grouped conceptually into a few
  17390.    major categories (Figure 17-28 on pp. 386─89):
  17391.  
  17392.    ■  Process management
  17393.  
  17394.    ■  Hardware management
  17395.  
  17396.    ■  Memory management
  17397.  
  17398.    ■  Monitor management
  17399.  
  17400.    ■  Semaphore management
  17401.  
  17402.    ■  Request packet management
  17403.  
  17404.    ■  Character queue management
  17405.  
  17406.    ■  Timer management
  17407.  
  17408.    ■  Miscellaneous device helpers
  17409.  
  17410.    ■  ABIOS-related device helpers (IBM PS/2 only)
  17411.  
  17412.    ──────────────────────────────────────────────────────────────────────────
  17413.    AllocPhys  equ  18h             ; DevHlp function number
  17414.  
  17415.  
  17416.    devhlp  dd      ?               ; bimodal pointer to
  17417.                                    ; DevHlp common entry point
  17418.                                    ; (from Init routine)
  17419.  
  17420.    bufptr  dd      ?               ; receives 32-bit physical
  17421.                                    ; address of allocated block
  17422.  
  17423.            .
  17424.            .
  17425.            .
  17426.                                    ; allocate 64 KB of
  17427.                                    ; memory above 1 MB...
  17428.            mov     ax,1            ; AX = high word of size
  17429.            mov     bx,0            ; BX = low word of size
  17430.            mov     dh,0            ; 0 = above 1 MB boundary
  17431.            mov     dl,AllocPhys    ; DevHlp 18H = AllocPhys
  17432.  
  17433.            call    devhlp          ; indirect call to DevHlp
  17434.                                    ; common entry point
  17435.  
  17436.            jc      error           ; jump if allocation failed
  17437.  
  17438.                                    ; save 32-bit physical
  17439.                                    ; address of memory block
  17440.            mov     word ptr bufptr,bx
  17441.            mov     word ptr bufptr+2,ax
  17442.            .
  17443.            .
  17444.            .
  17445.    ──────────────────────────────────────────────────────────────────────────
  17446.  
  17447.    Figure 17-27.  Example of DevHlp call. This code allocates 64 KB of
  17448.    physical memory above the 1 MB boundary for use by driver. When the driver
  17449.    wishes to access the memory block, it must use PhysToVirt or PhysToUVirt
  17450.    to convert the physical address stored in bufptr into an appropriate
  17451.    virtual address for the current CPU mode.
  17452.  
  17453. ╓┌─┌─────────────────────┌───────┌─────────────┌─────────────────────────────╖
  17454.    DevHlp Name           DevHlp      Modes     DevHlp Action
  17455.                          Code
  17456.    ──────────────────────────────────────────────────────────────────────────
  17457.    Process Management
  17458.    Block                04H     K     U       Blocks the calling thread
  17459.                                                until a timeout occurs or
  17460.                                                thread is unblocked by Run.
  17461.    DevDone              01H     K Int         Signals that the specified
  17462.                                                operation has completed.
  17463.                                                Kernel unblocks any threads
  17464.                                                waiting for the operation.
  17465.    Run                  05H     K Int U       Activates a thread which was
  17466.                                                previously suspended by a call
  17467.                                                to Block.
  17468.    TCYield              03H     K             Similar to Yield, but allows
  17469.                                                other threads to execute only
  17470.                                                if they have a time-critical
  17471.                                                priority.
  17472.    Yield                02H     K             Surrenders the CPU to any
  17473.                                                other threads of the same or
  17474.    DevHlp Name           DevHlp      Modes     DevHlp Action
  17475.                          Code
  17476.    ──────────────────────────────────────────────────────────────────────────
  17477.                                               other threads of the same or
  17478.                                                higher priority which are
  17479.                                                ready to execute.
  17480.  
  17481.  
  17482.    Hardware Management
  17483.    EOI                  31H       Int   Init  Issues an End-of-Interrupt to
  17484.                                                the appropriate 8259 PIC for
  17485.                                                the specified IRQ level.
  17486.    RegisterStackUsage   38H             Init  Declares stack requirements
  17487.                                                for driver's interrupt
  17488.                                                handler.
  17489.    SetIRQ               1BH     K       Init  Sets the interrupt vector for
  17490.                                                the indicated interrupt
  17491.                                                request (IRQ) level to point
  17492.                                                to the driver's interrupt
  17493.                                                handler.
  17494.    UnSetIRQ             1CH     K Int   Init  Releases the interrupt vector
  17495.    DevHlp Name           DevHlp      Modes     DevHlp Action
  17496.                          Code
  17497.    ──────────────────────────────────────────────────────────────────────────
  17498.   UnSetIRQ             1CH     K Int   Init  Releases the interrupt vector
  17499.                                                for the specified IRQ level.
  17500.  
  17501.  
  17502.    Memory Management
  17503.    AllocGDTSelector     2DH             Init  Allocates one or more GDT
  17504.                                                selectors for use by the
  17505.                                                driver.
  17506.    AllocPhys            18H     K       Init  Allocates a block of fixed
  17507.                                                (not swappable or movable)
  17508.                                                memory. Caller can specify
  17509.                                                whether the block should be
  17510.                                                above or below the 1 MB
  17511.                                                boundary.
  17512.    FreePhys             19H     K       Init  Releases a block of memory
  17513.                                                previously allocated with
  17514.                                                AllocPhys.
  17515.    Lock                 13H     K       Init  Locks a memory segment, that
  17516.    DevHlp Name           DevHlp      Modes     DevHlp Action
  17517.                          Code
  17518.    ──────────────────────────────────────────────────────────────────────────
  17519.   Lock                 13H     K       Init  Locks a memory segment, that
  17520.                                                is, flags the segment as
  17521.                                                nonmovable and nonswappable.
  17522.    PhysToGDTSelector    2EH     K Int U Init  Maps a physical address and
  17523.                                                length onto a GDT selector.
  17524.    PhysToUVirt          17H     K       Init  Converts a 32-bit physical
  17525.                                                address to a virtual address
  17526.                                                accessed through the current
  17527.                                                Local Descriptor Table (LDT).
  17528.    PhysToVirt           15H     K Int   Init  Converts a 32-bit physical
  17529.                                                (linear) address to a virtual
  17530.                                                address.
  17531.    Unlock               14H     K       Init  Unlocks a memory segment.
  17532.    UnPhysToVirt         32H     K Int   Init  Signals that the virtual
  17533.                                                addresses previously obtained
  17534.                                                with PhysToVirt can be reused.
  17535.    VerifyAccess         27H     K             Verifies that a user process
  17536.                                                has access to a segment.
  17537.    DevHlp Name           DevHlp      Modes     DevHlp Action
  17538.                          Code
  17539.    ──────────────────────────────────────────────────────────────────────────
  17540.                                               has access to a segment.
  17541.    VirtToPhys           16H     K       Init  Converts a virtual address
  17542.                                                (segment:offset or
  17543.                                                selector:offset) to a 32-bit
  17544.                                                physical address.
  17545.  
  17546.  
  17547.    Monitor Management
  17548.    DeRegister           21H     K             Removes a process from a
  17549.                                                monitor chain.
  17550.    MonFlush             23H     K             Removes all data from a
  17551.                                                monitor chain.
  17552.    MonitorCreate        1FH     K       Init  Creates or removes an empty
  17553.                                                monitor chain.
  17554.    MonWrite             22H     K Int U       Passes a data record to a
  17555.                                                monitor chain for filtering.
  17556.    Register             20H     K             Adds a process to a monitor
  17557.                                                chain.
  17558.    DevHlp Name           DevHlp      Modes     DevHlp Action
  17559.                          Code
  17560.    ──────────────────────────────────────────────────────────────────────────
  17561.                                               chain.
  17562.  
  17563.  
  17564.    Semaphore Management
  17565.    SemClear             07H     K Int U       Clears a semaphore, restarting
  17566.                                                any threads that were blocking
  17567.                                                on the semaphore.
  17568.    SemHandle            08H     K Int         Converts a process's handle
  17569.                                                for a system semaphore to a
  17570.                                                virtual address that the
  17571.                                                driver can use at interrupt
  17572.                                                time.
  17573.    SemRequest           06H     K     U       Claims (sets) a semaphore. If
  17574.                                                the semaphore is already set,
  17575.                                                blocks the thread until the
  17576.                                                semaphore is available.
  17577.  
  17578.    Request Packet Management
  17579.    DevHlp Name           DevHlp      Modes     DevHlp Action
  17580.                          Code
  17581.    ──────────────────────────────────────────────────────────────────────────
  17582.   Request Packet Management
  17583.    AllocReqPacket       0DH     K             Allocates a block of memory
  17584.                                                large enough to hold the
  17585.                                                largest possible request
  17586.                                                packet and returns a bimodal
  17587.                                                pointer.
  17588.    FreeReqPacket        0EH     K             Releases a request packet
  17589.                                                previously allocated with
  17590.                                                AllocReqPacket.
  17591.    PullParticular       0BH     K Int         Removes a selected request
  17592.                                                packet from the driver's
  17593.                                                linked list.
  17594.    PullReqPacket        0AH     K Int         Removes the oldest request
  17595.                                                packet from the driver's
  17596.                                                linked list.
  17597.    PushReqPacket        09H     K             Adds a request packet to the
  17598.                                                driver's linked list of
  17599.                                                packets.
  17600.    DevHlp Name           DevHlp      Modes     DevHlp Action
  17601.                          Code
  17602.    ──────────────────────────────────────────────────────────────────────────
  17603.                                               packets.
  17604.    SortReqPacket        0CH     K             Inserts a request packet into
  17605.                                                the driver's linked list of
  17606.                                                packets, sorted by sector
  17607.                                                number.
  17608.  
  17609.  
  17610.    Character Queue Management
  17611.    QueueFlush           10H     K Int U       Resets the pointers to the
  17612.                                                specified buffer.
  17613.    QueueInit            0FH     K Int U Init  Establishes a ring buffer for
  17614.                                                storage of characters and
  17615.                                                initializes its pointers.
  17616.    QueueRead            12H     K Int U       Returns and removes a
  17617.                                                character from the specified
  17618.                                                buffer.
  17619.    QueueWrite           11H     K Int U       Puts a character into the
  17620.                                                specified buffer.
  17621.    DevHlp Name           DevHlp      Modes     DevHlp Action
  17622.                          Code
  17623.    ──────────────────────────────────────────────────────────────────────────
  17624.                                               specified buffer.
  17625.  
  17626.  
  17627.    Timer Management
  17628.    ResetTimer           1EH     K Int   Init  Removes a timer tick handler
  17629.                                                from the system's list of such
  17630.                                                handlers.
  17631.    SchedClockAddr       00H     K       Init  Obtains the address of the
  17632.                                                kernel's routine to be called
  17633.                                                on each clock tick. Used only
  17634.                                                by clock driver.
  17635.    SetTimer             1DH     K       Init  Adds a timer tick handler, to
  17636.                                                be called on every timer tick,
  17637.                                                to the system's list of such
  17638.                                                handlers.
  17639.    TickCount            33H     K Int U Init  Adds a timer tick handler, to
  17640.                                                be called at specified
  17641.                                                intervals, to the system's
  17642.    DevHlp Name           DevHlp      Modes     DevHlp Action
  17643.                          Code
  17644.    ──────────────────────────────────────────────────────────────────────────
  17645.                                               intervals, to the system's
  17646.                                                list of such handlers, or
  17647.                                                modifies the interval at which
  17648.                                                an existing handler is called.
  17649.  
  17650.    Miscellaneous Device Helpers
  17651.    AttachDD             2AH     K       Init  Obtains inter-driver
  17652.                                                communication (IDC) entry
  17653.                                                point for another driver.
  17654.    GetDOSVar            24H     K       Init  Returns addresses of kernel
  17655.                                                variables and entry points.
  17656.    ProtToReal           30H     K Int         Switches the CPU into real
  17657.                                                mode.
  17658.    RealToProt           2FH     K Int         Switches the CPU into
  17659.                                                protected mode.
  17660.    ROMCritSection       26H           U       Protects ROM BIOS code from
  17661.                                                interrupts that might cause a
  17662.                                                context switch.
  17663.    DevHlp Name           DevHlp      Modes     DevHlp Action
  17664.                          Code
  17665.    ──────────────────────────────────────────────────────────────────────────
  17666.                                               context switch.
  17667.    SendEvent            25H     K Int         Signals the kernel that a key
  17668.                                                requiring special handling has
  17669.                                                been detected. Used only by
  17670.                                                keyboard driver.
  17671.    SetROMVector         1AH     K       Init  Captures an interrupt vector
  17672.                                                normally used in real mode to
  17673.                                                invoke a ROM BIOS function,
  17674.                                                returning the previous
  17675.                                                contents of the vector for
  17676.                                                chaining.
  17677.  
  17678.  
  17679.    ABIOS-related Device Helpers
  17680.    (IBM PS/2 only)
  17681.    ABIOSCall            36H     K Int U Init  Invokes an Advanced BIOS
  17682.                                                (ABIOS) service on behalf of
  17683.                                                the driver.
  17684.    DevHlp Name           DevHlp      Modes     DevHlp Action
  17685.                          Code
  17686.    ──────────────────────────────────────────────────────────────────────────
  17687.                                               the driver.
  17688.    ABIOSCommonEntry     37H     K Int U Init  Transfers to an ABIOS Common
  17689.                                                Entry point on behalf of the
  17690.                                                driver.
  17691.    FreeLIDEntry         35H     K     U Init  Releases a logical ID (LID)
  17692.                                                for a physical device.
  17693.    GetLIDEntry          34H     K     U Init  Obtains a logical ID (LID) for
  17694.                                                a physical device, for use
  17695.                                                with subsequent ABIOS calls.
  17696.    ──────────────────────────────────────────────────────────────────────────
  17697.  
  17698.  
  17699.    Figure 17-28.  DevHlp functions by category, their function numbers, and
  17700.    the driver contexts in which they may be used (K = kernel mode, Int =
  17701.    interrupt mode, U = user mode, Init = initialization mode).
  17702.  
  17703.    Some service categories are used by every OS/2 device driver because they
  17704.    allow the driver to perform overlapped, interrupt-driven I/O operations in
  17705.    a "well-behaved" manner, or because they provide access to memory
  17706.    addresses that the driver could not reach otherwise. The remainder are
  17707.    present only for convenience or to assist in the construction of unusual
  17708.    or highly complex drivers.
  17709.  
  17710.    The programming reference for the DevHlp services is in Section VI,
  17711.    beginning on p. 657.
  17712.  
  17713.  
  17714.  Process Management
  17715.  
  17716.    The basic technique by which a driver performs overlapped or asynchronous
  17717.    I/O is a simple one. When the strategy routine of the driver is called, it
  17718.    sets up the I/O operation (or adds it to the list of pending operations if
  17719.    one is already in progress) and simply returns to the OS/2 kernel with the
  17720.    Done bit cleared in the request packet status word. The kernel then
  17721.    suspends the thread that initiated the I/O request.
  17722.  
  17723.    When the I/O operation is eventually completed, the driver's interrupt
  17724.    routine updates the request packet that initiated the operation and then
  17725.    calls the DevHlp routine DevDone. This signals the kernel to awaken the
  17726.    thread that owns the I/O request, which can then perform any necessary
  17727.    post-I/O processing (such as translating the driver's status information
  17728.    into appropriate values to be returned to an application program).
  17729.  
  17730.    The driver can also use the DevHlp functions Block and Run to suspend a
  17731.    thread explicitly while an I/O operation is in progress, particularly if
  17732.    the driver must execute a lengthy amount of code after the operation is
  17733.    complete. Putting such code in the strategy routine is preferable to
  17734.    putting it in the interrupt routine because other lower-priority
  17735.    interrupts are locked out by the hardware while the interrupt routine is
  17736.    executing.
  17737.  
  17738.    The strategy routine sets up the I/O operation and then calls Block with a
  17739.    32-bit identifier of its own choosing. When the interrupt routine notes
  17740.    that the I/O operation is complete, it calls Run with the same 32-bit
  17741.    identifier. This causes the kernel to wake up the original thread within
  17742.    the strategy routine, which can proceed with post-I/O processing and then
  17743.    exit to the kernel with the request packet's Done bit set. The biggest
  17744.    problem with this method is ensuring the uniqueness of the 32-bit value
  17745.    that the driver uses to associate a pair of Block and Run calls; the call
  17746.    to Run wakes up all threads that have blocked with that particular 32-bit
  17747.    identifier.
  17748.  
  17749.    The additional DevHlp functions Yield and TCYield are provided for drivers
  17750.    that need to transfer long strings of data from one memory location to
  17751.    another at task time (such as a RAM disk) or whose devices do not support
  17752.    interrupts. A call to Yield simply tells the kernel's scheduler to give
  17753.    all other threads with the same or higher priority a chance to run before
  17754.    returning control to the currently executing thread, whereas TCYield
  17755.    allows only those other threads which have a "time-critical priority" to
  17756.    take a turn.
  17757.  
  17758.    A driver can check the value of YieldFlag or TCYieldFlag──two variables
  17759.    whose addresses are returned by another DevHlp, GetDOSVar──to determine
  17760.    whether any other threads are waiting for the CPU, and it can then bypass
  17761.    the call to Yield or TCYield if no other thread is eligible to execute. To
  17762.    avoid interference with the proper operation of other drivers, make such a
  17763.    check (and possible Yield) at least every 3 milliseconds.
  17764.  
  17765.  Hardware Management
  17766.  
  17767.    Because the strategy and interrupt routines of device drivers execute at
  17768.    the highest privilege level, they have a free hand with the machine if
  17769.    they want it; two different drivers that access the same hardware
  17770.    addresses can theoretically come into conflict. However, OS/2 provides
  17771.    DevHlps to arbitrate interrupt vector and 8259 programmable interrupt
  17772.    controller (PIC) usage for cooperating drivers. Drivers that do not use
  17773.    these DevHlps to gain access to the hardware resources they need might not
  17774.    work properly on future 80386-specific versions of OS/2.
  17775.  
  17776.    The DevHlp functions SetIRQ and UnSetIRQ assist drivers in the capture or
  17777.    release of interrupt vectors. When a driver calls SetIRQ, it also
  17778.    specifies whether it is willing to share the level with another driver. If
  17779.    it specifies that it wants to own the interrupt exclusively, subsequent
  17780.    requests for the same IRQ level by other drivers will fail; if another
  17781.    driver has already signed up for the interrupt, the request by the current
  17782.    driver will fail. In OS/2 version 1.1 a driver that uses interrupts must
  17783.    also declare the stack requirements of its interrupt handler during
  17784.    initialization by calling the DevHlp RegisterStackUsage.
  17785.  
  17786.    When a hardware interrupt occurs, a dispatcher in the OS/2 kernel
  17787.    initially receives control, saves all registers, sets the DS register to
  17788.    point to the driver's near data segment, and then transfers control to the
  17789.    driver's interrupt handler. The handler services its device, starts
  17790.    another I/O operation if any are waiting, calls the DevHlp function EOI to
  17791.    dismiss the interrupt at the 8259 PIC, and then returns to the kernel.
  17792.    Although a driver can access its own device's ports directly, you should
  17793.    reserve manipulation of the PIC for the kernel to isolate a driver from
  17794.    the interrupt architecture.
  17795.  
  17796.  Memory Management
  17797.  
  17798.    A driver specifies the amount of storage to reserve for its code and data
  17799.    segments in the information it returns to the kernel at Init time. If the
  17800.    driver needs additional memory for tables, buffers, or other data
  17801.    structures, it can use the DevHlp functions AllocPhys and FreePhys to
  17802.    dynamically allocate and release such memory. The memory assigned to the
  17803.    driver by AllocPhys is not movable or swappable, and the driver can
  17804.    specify whether it wants the memory to be located above or below the 1 MB
  17805.    boundary.
  17806.  
  17807.    Memory addressing is one of the trickiest aspects of an OS/2 driver's
  17808.    operation. To execute and handle interrupts in either protected mode or
  17809.    real mode, a driver must be prepared to encounter three types of
  17810.    addresses──protected mode selector:offset pairs, real mode segment:offset
  17811.    pairs, and the physical 32-bit addresses used by DMA channels and by some
  17812.    devices. Further complicating the situation are the activities of the
  17813.    operating system's virtual memory manager, which shuffles segments around
  17814.    to collect unused fragments and swaps out segments to disk.
  17815.  
  17816.    The OS/2 kernel takes several measures to shield drivers from most of the
  17817.    potential problems with memory addressing. First, whenever the kernel
  17818.    requests an explicit transfer operation from a driver using a request
  17819.    packet that contains the Read or Write command codes, it passes the driver
  17820.    a 32-bit physical address which has already been "locked" (meaning that
  17821.    the virtual memory manager has been notified that the memory in question
  17822.    should not be moved or swapped). In addition, the kernel uses bimodal
  17823.    pointers when it passes the driver an address of a structure (such as a
  17824.    request packet) or procedure (such as the DevHlp entry point) that the
  17825.    driver needs to access directly.
  17826.  
  17827.    The kernel also provides six DevHlp functions that help a driver make
  17828.    address conversions: AllocateGDTSelector, PhysToGDTSelector, PhysToUVirt,
  17829.    PhysToVirt, UnPhysToVirt, and VirtToPhys. These functions assist a driver
  17830.    by converting physical 32-bit addresses to virtual addresses
  17831.    (segment:offset or selector:offset pairs) and back again. The first three
  17832.    are used to access static structures (such as memory allocated by
  17833.    AllocPhys or an adapter's memory-mapped I/O locations). PhysToVirt and
  17834.    UnPhysToVirt are used when a driver needs temporary access to data that is
  17835.    transient or owned by a process, allowing the driver to translate the
  17836.    physical address in a Read or Write request into a virtual address
  17837.    appropriate to the current CPU mode.
  17838.  
  17839.    Finally, to provide for cases in which a memory address is passed to a
  17840.    driver in an IOCTL data packet or by some other channel of communication,
  17841.    the OS/2 kernel also provides the DevHlp functions Lock, Unlock, and
  17842.    VerifyAccess. The driver first calls VerifyAccess with the selector,
  17843.    offset, and length of the memory involved in the requested operation to
  17844.    confirm that the application is entitled to access that memory. If
  17845.    VerifyAccess succeeds, the driver calls the Lock function to immobilize
  17846.    the segment and VirtToPhys to obtain the physical address if necessary;
  17847.    then it proceeds with the I/O operation, calling Unlock afterwards to put
  17848.    the segment back under the control of the system's memory manager. Drivers
  17849.    must avoid blocking between the VerifyAccess and Lock calls so that no
  17850.    context switch can occur that might make the VerifyAccess result invalid.
  17851.  
  17852.    The use of PhysToVirt is most interesting when the driver is entered in
  17853.    real mode while the memory address to be accessed lies above the 1 MB
  17854.    boundary. On an IBM AT─type machine, PhysToVirt uses an undocumented 80286
  17855.    instruction (LOADALL) to set the CPU's "shadow" descriptor registers with
  17856.    values that allow the memory access to take place without a mode switch.
  17857.    The addressability of the memory location is valid only as long as the
  17858.    associated segment register (which does not contain any physically
  17859.    meaningful value) is not reloaded; thus, interrupts must remain masked,
  17860.    the segment register should not be pushed and popped, and no DevHlp calls
  17861.    other than another PhysToVirt can be made until the driver completes the
  17862.    memory access.
  17863.  
  17864.    On an 80386-based or IBM PS/2─type machine, which supports faster mode
  17865.    switching, PhysToVirt switches the CPU into protected mode and returns a
  17866.    normal selector for the memory location of interest. At first glance, this
  17867.    method of obtaining addressability for the data may appear more
  17868.    attractive, but it actually imposes a much greater burden on the driver
  17869.    because it invalidates any pointers previously obtained from PhysToVirt
  17870.    that did not cause a mode switch. The driver must be constantly on the
  17871.    lookout for an unexpected change in modes and must be prepared to
  17872.    backtrack and recalculate any saved virtual addresses.
  17873.  
  17874.    In either hardware environment, the pool of selectors available to
  17875.    PhysToVirt is limited. When a program finishes with virtual addresses it
  17876.    obtained from PhysToVirt, it should notify the system that the selectors
  17877.    can be reused by calling UnPhysToVirt. If an earlier call to PhysToVirt
  17878.    resulted in a mode switch, UnPhysToVirt also restores the original CPU
  17879.    mode.
  17880.  
  17881.  Monitor Management
  17882.  
  17883.    The five monitor-related DevHlps──MonitorCreate, Register, DeRegister,
  17884.    MonWrite, and MonFlush──help the driver carry out its role in OS/2's
  17885.    support for device monitors. MonitorCreate establishes or destroys a chain
  17886.    of monitor buffers; Register and DeRegister add or remove a specific
  17887.    application's buffers from the monitor buffer chain.
  17888.  
  17889.    The driver passes characters through the monitor buffer chain by calling
  17890.    MonWrite. The kernel calls a notification routine within the driver when
  17891.    data emerges from the end of the buffer chain. The driver can notify all
  17892.    device monitor applications to reinitialize their pointers and buffers by
  17893.    calling MonFlush.
  17894.  
  17895.    Chapter 18 covers device monitors, as well as device driver support for
  17896.    monitors, in more detail.
  17897.  
  17898.  Semaphore Management
  17899.  
  17900.    The DevHlp functions SemRequest and SemClear, which can be called by the
  17901.    driver to obtain or release ownership of a semaphore, are the equivalent
  17902.    of the API functions DosSemRequest and DosSemClear described in Chapter
  17903.    13. These operate on either RAM semaphores or system semaphores.
  17904.  
  17905.    RAM semaphores can be located in the data segment of either a driver or an
  17906.    application program. (When an application RAM semaphore is used, its
  17907.    address would typically be passed to the driver in a DosDevIOCtl call.)
  17908.    RAM semaphores are useful for signaling between different components of a
  17909.    driver or between a closely coupled driver and application, or for
  17910.    synchronizing access to driver resources that are nonreentrant.
  17911.  
  17912.    System semaphores can be created or opened only by an application, not by
  17913.    a driver. To use system semaphores for communication between a driver and
  17914.    an application, the application must first obtain a handle for the
  17915.    semaphore with DosOpenSem or DosCreateSem and pass it to the driver with a
  17916.    DosDevIOCtl call. The driver then uses the DevHlp function SemHandle at
  17917.    task time to obtain a new semaphore handle that can be used at interrupt
  17918.    time. Once both the driver and application have a usable handle for the
  17919.    semaphore, either can use the semaphore to signal or block the other.
  17920.  
  17921.  Request Packet Management
  17922.  
  17923.    Another set of DevHlps assists the driver in maintaining and sorting a
  17924.    linked list of request packets for pending I/O operations. The driver must
  17925.    provide a doubleword of storage (initialized to zero) that the DevHlp
  17926.    routines can use as a pointer to the head of the request packet queue. A
  17927.    reserved doubleword field within the packets is used to chain one to
  17928.    another.
  17929.  
  17930.    Request packets are added to the queue by PushReqPacket (which adds to the
  17931.    queue end) or by SortReqPacket (which inserts a Read or Write packet into
  17932.    the queue based on its logical sector number). A driver can remove packets
  17933.    from the queue with PullReqPacket (which returns the oldest request) or
  17934.    with PullParticular (usually employed only when a process has terminated
  17935.    with I/O still pending).
  17936.  
  17937.    The DevHlp functions AllocReqPacket and FreeReqPacket let a driver
  17938.    dynamically obtain and release storage for additional request packets. A
  17939.    driver might use these routines in cases where it needs to decompose an
  17940.    I/O request into two or more requests that it can queue independently──for
  17941.    example, it might convert a Read request packet into a seek followed by a
  17942.    transfer to avoid issuing redundant seek commands if multiple Read
  17943.    requests are pending for the same track. AllocReqPacket returns a bimodal
  17944.    pointer to a block of memory that is exactly large enough for the largest
  17945.    possible request packet (in contrast to AllocPhys, which returns a 32-bit
  17946.    physical address and can be used to allocate blocks of any size).
  17947.  
  17948.    Request packets are a relatively scarce resource. A driver that uses
  17949.    AllocReqPacket should be prepared for the function to fail and should have
  17950.    a recovery strategy that will not degrade system performance or cause I/O
  17951.    requests to fail.
  17952.  
  17953.  Character Queue Management
  17954.  
  17955.    The kernel also supplies a group of DevHlps for managing character queues;
  17956.    these routines provide simple support for a ring buffer of characters
  17957.    which are received or transmitted from a device.
  17958.  
  17959.    A character queue is declared in the following form:
  17960.  
  17961.    QSize   dw      n               ; size of queue in bytes
  17962.    QChrOut dw      0               ; index of next character out
  17963.    QCount  dw      0               ; count of characters in buffer
  17964.    Qbuff   db      n dup (?)       ; storage starts here
  17965.  
  17966.    Each character queue has, in effect, two pointers: an "out" pointer, which
  17967.    points to the oldest character in the buffer, and an "in" pointer, which
  17968.    points to the buffer position for the next character to be added to the
  17969.    buffer. When both pointers are equal, the buffer is empty. These two
  17970.    pointers do not exist in storage but are formed as needed by combining the
  17971.    values in QChrOut and QCount with the base address Qbuff.
  17972.  
  17973.    A character queue is initialized by a call to the DevHlp function
  17974.    QueueInit. You can add characters to the ring buffer with QueueWrite and
  17975.    remove them with the DevHlp function QueueRead. The QueueFlush routine
  17976.    resets the pointers to the specified character queue, effectively
  17977.    discarding its contents.
  17978.  
  17979.    A typical character device driver has two character queues: one for
  17980.    received characters and one for transmitted characters. The driver's
  17981.    interrupt routine adds characters to the receive queue, and its strategy
  17982.    routine removes them with a call to the Read (command code 4) function.
  17983.    Characters are added to the write queue by the strategy routine's Write
  17984.    (command code 8) function if the device is busy or if characters are
  17985.    already waiting in the queue, and they are removed from the queue by the
  17986.    interrupt routine.
  17987.  
  17988.  Timer Management
  17989.  
  17990.    The timer-related DevHlps assist a driver in performing polled I/O on
  17991.    devices that do not support interrupts, or in setting up a "watchdog"
  17992.    timer to detect lost interrupts or I/O operations that never complete.
  17993.  
  17994.    The function SetTimer is called with the address of a routine within the
  17995.    driver that must execute on each timer tick; this is the equivalent of
  17996.    chaining onto Int 1CH (the ROM BIOS timer tick vector) under MS-DOS. The
  17997.    DevHlp function TickCount is a more general version of SetTimer; rather
  17998.    than entering the driver's timer tick handler on every tick, it lets you
  17999.    specify the number of ticks between entries. The driver can call
  18000.    ResetTimer to remove its timer tick handler from the system's list of all
  18001.    such handlers.
  18002.  
  18003.    The function SchedClockAddr is used only by the system's clock driver
  18004.    during its initialization. It supplies a pointer to the routine in the
  18005.    kernel that should be called on each clock tick. The kernel's routine then
  18006.    distributes the clock ticks through the system wherever they are needed──
  18007.    to the scheduler, for example, and to the timer tick handlers of other
  18008.    device drivers that have been registered with SetTimer or TickCount.
  18009.  
  18010.  Miscellaneous Device Helpers
  18011.  
  18012.    The remaining DevHlps──RealToProt, ProtToReal, SendEvent, GetDOSVar,
  18013.    SysTrace, SetROMVector, ROMCritSection, AttachDD, and the ABIOS-related
  18014.    services──do not fall into any convenient category.
  18015.  
  18016.    As their names imply, RealToProt and ProtToReal allow the driver to
  18017.    request a CPU mode switch. Placing the mode-switching logic inside the
  18018.    kernel ensures that the mode switching is done properly and that it takes
  18019.    advantage of any special external hardware support (such as exists on the
  18020.    PS/2) or CPU support (such as that provided by the 80386) that can speed
  18021.    up the operation.
  18022.  
  18023.    SendEvent is normally used only by the system's keyboard driver (KBD$). It
  18024.    allows the driver to signal the kernel when certain special keys are
  18025.    detected (such as Ctrl-Break or the Task Manager hot keys), so that the
  18026.    kernel can take immediate action rather than waiting for the keycode to
  18027.    percolate through the driver's character input buffer.
  18028.  
  18029.    GetDOSVar returns bimodal addresses for several significant system
  18030.    variables and tables, including the Global Information Segment, the Local
  18031.    Information Segment, the "reboot" routine, and the system's Yield and
  18032.    TCYield flags (set when at least one thread is ready to execute). The
  18033.    driver can call GetDOSVar to obtain the pointers at its initialization
  18034.    time and store them for later use.
  18035.  
  18036.    AttachDD allows a driver to obtain the inter-driver communication (IDC)
  18037.    entry point for a previously loaded character device driver. It can then
  18038.    invoke the other driver directly at any time with a far call. Parameter
  18039.    passing for IDC can take any form and is not standardized because the OS/2
  18040.    kernel is not involved. AttachDD identifies drivers by the 8-character
  18041.    device name in their headers, so a block driver that wants to make an IDC
  18042.    entry point available needs two headers: one for the usual purposes and a
  18043.    second that has the character device attribute bit set and that contains a
  18044.    logical device name and the IDC entry point.
  18045.  
  18046.    SetROMVector and ROMCritSection assist drivers──such as the keyboard,
  18047.    video, serial port, and printer drivers──which must support ROM BIOS calls
  18048.    by real mode applications; that is, drivers that can be entered in user
  18049.    mode. At initialization time, the driver must use SetROMVector to take
  18050.    over the software interrupt vector for the ROM BIOS services that use its
  18051.    device. When a real mode program makes a ROM BIOS call, the driver
  18052.    receives control, at which point it can use ROMCritSection to disable
  18053.    session switching, and then chain to the original ROM BIOS code or provide
  18054.    the same functionality within its own code.
  18055.  
  18056.  
  18057.  The Processing of a Typical I/O Request
  18058.  
  18059.    An application program requests an I/O operation from OS/2 by pushing
  18060.    appropriate values and addresses onto the stack and executing a far call
  18061.    to a named API entry point. OS/2 inspects its internal tables, searches
  18062.    the chain of device driver headers if necessary, and determines which
  18063.    device driver should receive the I/O request.
  18064.  
  18065.    OS/2 then creates a request packet in a reserved area of memory. The
  18066.    system transforms disk I/O requests from file and record information into
  18067.    logical sector requests based on its interpretation of the disk's
  18068.    directory and file allocation table. (OS/2 can locate these disk
  18069.    structures using the information that is returned by the driver from a
  18070.    previous Build BPB call, and it issues additional driver read requests if
  18071.    necessary to bring their sectors into memory.)
  18072.  
  18073.    After the request packet is prepared, OS/2 calls the device driver's
  18074.    strategy entry point, passing the address of the packet in registers
  18075.    ES:BX. If the strategy routine can dispose of the request immediately (as
  18076.    it typically can with a status request or a request for data which is
  18077.    already in the driver's own buffers), it places the appropriate
  18078.    information into the request packet or other buffer, sets the Done bit to
  18079.    indicate that the request is complete, and returns control to the kernel.
  18080.  
  18081.    Otherwise, the strategy routine can suspend execution of the requesting
  18082.    thread by two different methods. It can return to the kernel with the Done
  18083.    bit in the request header cleared and rely on the interrupt routine to
  18084.    call DevDone later. This method has the side effect of unblocking the
  18085.    thread that issued the I/O request. Alternatively, it can call the DevHlp
  18086.    Block and wait for the interrupt routine to wake it up by calling the
  18087.    DevHlp Run. In this case, the strategy routine sets the Done bit in the
  18088.    request packet status word before it exits, and no call to DevDone is
  18089.    needed.
  18090.  
  18091.    When the I/O is finished, the kernel transforms the request status to an
  18092.    application level error code if necessary and updates the application's
  18093.    saved AX register with the appropriate status code. It places any other
  18094.    necessary information (such as the number of bytes actually transferred on
  18095.    a DosRead call) into the variables whose addresses were passed on the
  18096.    stack in the original API call. At this point the thread which made the
  18097.    I/O request exits the kernel and resumes execution within the application.
  18098.    Figure 17-29 provides a sketch of this entire flow of control and data.
  18099.  
  18100.                              ┌───────────────────────┐
  18101.                              │  Application program  │
  18102.                              └────┬─────────────────┘
  18103.    Far call to API dynamic link   │             │  Read status returned in
  18104.    entry point for DosRead        │             │  register AX; length passed
  18105.                                   │             │  in caller's variable
  18106.                                   │             │
  18107.                                   │             │
  18108.                              ┌─────────────────┴────┐
  18109.                              │      OS/2 kernel      │
  18110.                              └────┬─────────────────┘
  18111.    Far call to strategy entry     │             │  Status returned to OS/2 in
  18112.    point ES:BX = request packet   │             │  request packet; data
  18113.                                   │             │  placed with command
  18114.                                   │             │  code = 4 at address
  18115.                                   │             │  indicated by kernel
  18116.                              ┌─────────────────┴────┐
  18117.                              │     Device driver     │
  18118.                              └────┬─────────────────┘
  18119.    Commands issued to I/O ports;  │             │  Data transferred from
  18120.    request read of physical head, │             │  device to memory
  18121.    track, sector                  │             │
  18122.                                   │             │
  18123.                                   │             │
  18124.                              ┌─────────────────┴────┐
  18125.                              │    Physical device    │
  18126.                              └───────────────────────┘
  18127.  
  18128.    Figure 17-29.  The processing of a typical I/O request from an application
  18129.    program.
  18130.  
  18131.    Note that a single API call by an application program can result in many
  18132.    requests from the kernel to the device driver. For example, if an
  18133.    application invokes DosOpen to open or create a file, OS/2 might have to
  18134.    issue multiple sector Read requests to the driver while searching the
  18135.    directory for the filename. Similarly, a DosRead request on an open file
  18136.    might obligate OS/2 to load several FAT sectors while locating the correct
  18137.    cluster, before the actual file data can be transferred into memory.
  18138.  
  18139.  
  18140.  Building a Device Driver
  18141.  
  18142.    The creation of a full-fledged OS/2 device driver, particularly a driver
  18143.    for a high speed mass storage device with multiple units, is a large and
  18144.    complex project. Because a driver can contain multiple threads of
  18145.    execution at any given time, you must guard against the subtle bugs that
  18146.    result from improper handling of interrupts, incorrect synchronization of
  18147.    driver resources, race conditions, and routines that are not completely
  18148.    reentrant. In accordance with Murphy's Law, such bugs often go
  18149.    undiscovered until the driver is subjected to the end user's typical job
  18150.    mix, which stresses the driver in ways you never imagined.
  18151.  
  18152.    You can minimize the unpleasant surprises in your OS/2 device driver by
  18153.    adopting an approach that stresses defensive, conservative, structured
  18154.    coding techniques and stepwise refinement. These techniques are valuable
  18155.    in any software development project, of course, but they can make the
  18156.    difference between success and failure in driver development.
  18157.  
  18158.  Segments and Subroutines
  18159.  
  18160.    OS/2 device drivers are usually written as "small model" Microsoft MASM
  18161.    programs with one code segment and one data segment. The data segment,
  18162.    with the device driver header at its beginning, should be declared before
  18163.    the code segment. The structure of the resulting executable file built by
  18164.    the Linker is shown in Figure 17-30 on the following page. The driver
  18165.    should not contain a stack segment──a stack is provided by the system.
  18166.  
  18167.    The two primary components of a driver are its strategy and interrupt
  18168.    routines. They, in turn, call other routines which implement the various
  18169.    driver functions (read, write, status, and so forth), allocate or release
  18170.    memory, translate addresses, and manage the driver's request queue. You
  18171.    should design each procedure to be reentrant, if possible, even if you see
  18172.    no obvious need for reentrancy; it is much easier to code a routine
  18173.    properly that uses local variables from the outset than to graft them on
  18174.    later. Any action on global data items by a procedure should be clearly
  18175.    documented and synchronized (if necessary) by semaphores.
  18176.  
  18177.    Start ┌─────────────────────────────────┐
  18178.       of  │         EXE file header         │
  18179.     file  ├─────────────────────────────────┤──┐
  18180.           │      Device driver header       │  │
  18181.           ├─────────────────────────────────┤  │   Driver
  18182.          ─┴─                               ─┴─ ├── data
  18183.          ─┬─     Resident driver data      ─┬─ │   segment
  18184.           ├─────────────────────────────────┤  │
  18185.           │ Initialization data (discarded) │  │
  18186.           ├─────────────────────────────────┤══╡
  18187.          ─┴─                               ─┴─ │   Driver
  18188.          ─┬─     Resident driver code      ─┬─ ├── code
  18189.      End  ├─────────────────────────────────┤  │   segment
  18190.       of  │ Initialization code (discarded) │  │
  18191.     file └─────────────────────────────────┘──┘
  18192.  
  18193.    Figure 17-30.  Structure of an OS/2 installable device driver file. OS/2
  18194.    drivers are linked as "small model" EXE files, containing one code segment
  18195.    and one data segment. By convention, driver files always have the
  18196.    extension SYS. Note that code and data used only by the driver's
  18197.    initialization routine can be positioned at the end of the two segments.
  18198.    To minimize the driver's memory overhead, they can then be discarded after
  18199.    driver initialization is completed.
  18200.  
  18201.  Stepwise Refinement #1
  18202.  
  18203.    The first version of a new device driver should perform all its operations
  18204.    by polled I/O within the strategy routine, that is, at task time. By
  18205.    starting out with a polled I/O version, you can validate the elements of
  18206.    the driver which are purely related to control of the hardware──issuing
  18207.    the right commands to the correct I/O ports in the proper order──without
  18208.    worrying about CPU mode changes, reentrancy, and interrupts.
  18209.  
  18210.    The strategy routine runs with kernel privilege and with interrupts
  18211.    enabled; it has full access to all I/O ports and memory addresses and is
  18212.    never preempted. It is entered from the kernel with a valid stack,
  18213.    register DS pointing to the driver's data segment, and registers ES:BX
  18214.    containing a bimodal address of a request packet. The strategy routine
  18215.    exits to the kernel with a far return; no registers need be preserved.
  18216.  
  18217.    To simplify initial debugging of a new driver, you can implement a minimum
  18218.    set of functions──sufficient to read and write the physical device──and
  18219.    provide stubs for the less vital services. Depending on the service it
  18220.    represents, a stub can return a "reasonable" result, do nothing and return
  18221.    a Done status, or return an Error plus Done status.
  18222.  
  18223.    Character device drivers must support, at minimum, the device driver
  18224.    command code functions Init, Read, and Write. Write with Verify can be
  18225.    aliased to Write, and DeInstall can return an error. If your driver
  18226.    performs internal buffering of data, you should also fully implement Input
  18227.    Status, Output Status, Nondestructive Read, Flush Input Buffers, and Flush
  18228.    Output Buffers. If your driver does not provide internal buffering, these
  18229.    five functions can simply return with the status word set to 0100H (Busy
  18230.    bit clear, Done bit set). You need not provide support for the Device Open
  18231.    and Device Close functions unless bit 11 is set in the device header
  18232.    attribute word. The Generic IOCtl function should be stubbed out to return
  18233.    an error; leave the actual implementation for later.
  18234.  
  18235.    In a rudimentary block device driver, you must implement functions Init,
  18236.    Media Check, Build BPB, Read, and Write to support file storage by OS/2.
  18237.    As with the character drivers, you can temporarily alias Write with Verify
  18238.    to Write. Reset Media can simply return Done status, Get Logical Device
  18239.    and Set Logical Device should clear the unit field of the request packet
  18240.    to zero and return Done status, and the Partitionable Fixed Disk and Get
  18241.    Fixed Disk/Logical Unit Map functions can be stubbed out to return an
  18242.    error. The Device Open, Device Close, and Removable Media functions need
  18243.    not be implemented unless bit 11 is set in the header attribute word.
  18244.  
  18245.  Stepwise Refinement #2
  18246.  
  18247.    Once you establish that the basic device control works properly in a
  18248.    polled I/O environment, add interrupt handling to the driver. This change
  18249.    enlarges and complicates the driver considerably. Multiple requests might
  18250.    be pending at one time, and both the strategy and interrupt routines must
  18251.    be capable of manipulating the request list and initiating I/O. In
  18252.    addition, the CPU mode at strategy time for a given request might well be
  18253.    different than the mode at interrupt time.
  18254.  
  18255.    Like the strategy routine, the interrupt routine runs with kernel
  18256.    privilege and has full access to all I/O ports and memory addresses. It is
  18257.    entered by a far call from the kernel with a valid stack and with register
  18258.    DS pointing to the driver's data segment. If the driver owns the interrupt
  18259.    level exclusively, the interrupt routine is entered with interrupts
  18260.    masked; otherwise, it is entered with interrupts enabled.
  18261.  
  18262.    The interrupt routine may itself be interrupted only by other devices with
  18263.    higher priority, or by devices with the same priority (including its own
  18264.    device) once an EOI has been signaled to the 8259 PIC. The interrupt
  18265.    routine must test its device to verify that it generated the interrupt. If
  18266.    it owns the interrupt, it must service the device, start another I/O
  18267.    operation if any are waiting, call the DevHlp function EOI to dismiss the
  18268.    interrupt at the 8259 PIC, and return to the kernel with the Carry flag
  18269.    clear (0). If it does not own the interrupt, it should exit to the kernel
  18270.    with the Carry flag set (1).
  18271.  
  18272.    Your first priority when adding interrupt handling to your driver is to
  18273.    decide which command code functions can be completed entirely at task time
  18274.    by the strategy routine and which functions must be deferred to be
  18275.    completed by the interrupt routine. In most cases, for both character and
  18276.    block device drivers, only Read and Write requests need to be carried out
  18277.    in overlapped fashion; the request packets for all other driver operations
  18278.    can be processed when they are received.
  18279.  
  18280.    If the device is idle when the strategy routine is called with a Read or
  18281.    Write request, it should initiate the I/O immediately and then suspend the
  18282.    requesting thread so that the scheduler can dispatch other threads that
  18283.    are ready to execute while the I/O is in progress. If the device is busy,
  18284.    the strategy routine should simply add the request packet to the driver's
  18285.    request packet queue and then suspend the thread. To optimize access time,
  18286.    block device Read and Write requests can be queued and executed in any
  18287.    order, but character device Read and Write operations must be completed in
  18288.    the order they are received to avoid mixed output.
  18289.  
  18290.    As we have noted, the strategy routine can suspend execution of a
  18291.    requesting thread either by exiting to the kernel with the Done bit in the
  18292.    status word cleared or by explicitly putting the thread to sleep with a
  18293.    call to the DevHlp function Block. If the strategy routine blocks within
  18294.    itself, it must be fully reentrant because it might be reentered by the
  18295.    kernel as a result of new I/O requests from other threads. Any functions
  18296.    called by the strategy routine must be reentrant anyway if they can also
  18297.    be called by interrupt routines. The strategy routine should mask
  18298.    interrupts while it is manipulating its work list and while it is checking
  18299.    the device status so that it does not get into a race condition with its
  18300.    own interrupt handler.
  18301.  
  18302.  Stepwise Refinement #3
  18303.  
  18304.    In the final development step, tune the driver to optimize scheduling of
  18305.    multiple pending I/O requests, to minimize both the periods of interrupt
  18306.    masking and total time to service an interrupt, and to yield the CPU at
  18307.    suitable intervals (at least every 3 milliseconds) when large amounts of
  18308.    data must be shifted in memory. You can also add enhancements such as
  18309.    watchdog timers for lost interrupts and DosDevIOCtl support once the
  18310.    driver has proven fundamentally sound.
  18311.  
  18312.    Custom replacements for the system's keyboard, printer, and serial port
  18313.    drivers must support all the DosDevIOCtl functions documented in the OS/2
  18314.    manuals, so that applications which rely on these functions will not fail
  18315.    unexpectedly. IOCTL support for other custom drivers is generally
  18316.    optional, except that block device drivers should always support
  18317.    DosDevIOCtl Category 8, Function 63H (Get Device Parameters), which is
  18318.    used by CHKDSK. If this function is absent, CHKDSK terminates with the
  18319.    error message The System Cannot Accept the Command after displaying the
  18320.    logical drive's volume label and date.
  18321.  
  18322.  Drivers for Noninterrupting Devices
  18323.  
  18324.    Drivers whose devices are not capable of generating interrupts can perform
  18325.    polled I/O by two methods. If the peripheral is a DMA-capable block device
  18326.    or a relatively low speed character device, the driver can use the DevHlp
  18327.    functions TickCount or SetTimer to register a handler that receives
  18328.    control on each system timer tick (or multiple thereof), and it can have
  18329.    that handler poll the device. (The length of the timer tick in
  18330.    milliseconds can be obtained from the global information segment.) This
  18331.    relatively benign technique does not appreciably degrade system
  18332.    performance.
  18333.  
  18334.    If the device requires programmed I/O and transfers data in bursts which
  18335.    cannot be interrupted or deferred, or if it is a high speed character
  18336.    device, the driver must perform the I/O in a status-bound loop in the
  18337.    strategy routine. Such a driver almost inevitably degrades overall system
  18338.    performance because it interferes with the prompt initiation of I/O
  18339.    operations and the servicing of interrupts for other devices. The driver
  18340.    can keep the damage to a minimum by calling Yield or TCYield at intervals
  18341.    not greater than 3 milliseconds, even if this forces it to abort and
  18342.    restart some of its own I/O operations.
  18343.  
  18344.  
  18345.  A Sample Block Device Driver
  18346.  
  18347.    The listing TINYDISK.ASM (Figure 17-31 on the following page) contains
  18348.    the source code for a simple installable RAM disk called TINYDISK.SYS. Its
  18349.    module definition file, TINYDISK.DEF, is shown in Figure 17-32 on p. 417.
  18350.    This driver demonstrates the essential features of an OS/2 block device
  18351.    driver, and it illustrates the use of some crucial DevHlp functions such
  18352.    as AllocPhys and PhysToVirt.
  18353.  
  18354.    ──────────────────────────────────────────────────────────────────────────
  18355.            title   TINYDISK -- RAM Disk Device Driver
  18356.            page    55,132
  18357.            .286
  18358.  
  18359.    ;
  18360.    ; TINYDISK.ASM
  18361.    ;
  18362.    ; A sample OS/2 block driver that installs a 64 KB RAM disk.
  18363.    ;
  18364.    ; Assemble with:  C> masm tinydisk.asm;
  18365.    ; Link with:  C> link tinydisk,tinydisk.sys,,os2,tinydisk
  18366.    ;
  18367.    ; To install the  driver, add "DEVICE=TINYDISK.SYS" to CONFIG.SYS
  18368.    ; and reboot.
  18369.    ;
  18370.    ; Copyright (C) 1988 Ray Duncan
  18371.    ;
  18372.  
  18373.    maxcmd  equ     26              ; maximum allowed command code
  18374.  
  18375.    secsize equ     512             ; bytes/sector, IBM compatible media
  18376.  
  18377.    stdin   equ     0               ; standard device handles
  18378.    stdout  equ     1
  18379.    stderr  equ     2
  18380.  
  18381.    cr      equ     0dh             ; ASCII carriage return
  18382.    lf      equ     0ah             ; ASCII linefeed
  18383.  
  18384.    PhysToVirt   equ 15h            ; DevHlp services
  18385.    UnPhysToVirt equ 32h
  18386.    VerifyAccess equ 27h
  18387.    AllocPhys    equ 18h
  18388.  
  18389.            extrn   DosWrite:far
  18390.  
  18391.  
  18392.    DGROUP  group   _DATA
  18393.  
  18394.    _DATA   segment word public 'DATA'
  18395.                                    ; device driver header...
  18396.    header  dd      -1              ; link to next device driver
  18397.            dw      0080h           ; device attribute word
  18398.                                    ; bits 7-9 = driver level
  18399.            dw      Strat           ; Strategy entry point
  18400.            dw      0               ; reserved
  18401.            db      0               ; units (set by OS/2)
  18402.            db      15 dup (0)      ; reserved
  18403.  
  18404.    devhlp  dd      ?               ; DevHlp entry point
  18405.  
  18406.    wlen    dw      ?               ; receives DosWrite length
  18407.  
  18408.    dbase   dd      ?               ; 32-bit physical address,
  18409.                                    ; base of RAM disk storage
  18410.  
  18411.    xfrsec  dw      0               ; current sector for transfer
  18412.    xfrcnt  dw      0               ; sectors successfully transferred
  18413.    xfrreq  dw      0               ; number of sectors requested
  18414.    xfraddr dd      0               ; working address for transfer
  18415.  
  18416.    array   dw      bpb             ; array of pointers to BPB
  18417.                                    ; for each supported unit
  18418.  
  18419.    bootrec equ     $               ; logical sector 0 boot record
  18420.            jmp     $               ; JMP at start of boot sector,
  18421.            nop                     ; this field must be 3 bytes
  18422.            db      'IBM 10.1'      ; OEM identity field
  18423.                                    ; ---BIOS Parameter Block-----
  18424.    bpb     dw      secsize         ; 00H bytes per sector
  18425.            db      1               ; 02H sectors per cluster
  18426.            dw      1               ; 03H reserved sectors
  18427.            db      1               ; 05H number of FATs
  18428.            dw      64              ; 06H root directory entries
  18429.            dw      128             ; 08H total sectors
  18430.            db      0f8h            ; 0AH media descriptor
  18431.            dw      1               ; 0BH sectors per FAT
  18432.            dw      1               ; 0DH sectors per track
  18433.            dw      1               ; 0FH number of heads
  18434.            dd      0               ; 11H hidden sectors
  18435.            dd      0               ; 15H large total sectors (if
  18436.                                    ;     word at offset 08H = 0)
  18437.            db      6 dup (0)       ; 19H reserved
  18438.                                    ; ---End of BPB, 31 bytes-----
  18439.    bootrec_len equ $-bootrec       ; length of boot sector data
  18440.                                    ; additional words needed by
  18441.                                    ; Generic IOCTL Cat 8 Function 63H
  18442.                                    ; Get Device Parameters call
  18443.            dw      0               ; number of cylinders
  18444.            db      7               ; device type = unknown
  18445.            dw      1               ; device attribute word
  18446.    gdprec_len equ $-bpb            ; length of Generic IOCTL buffer
  18447.  
  18448.                                    ; Strategy routine dispatch table
  18449.                                    ; for request packet command code...
  18450.    dispch  dw      Init            ; 0  = initialize driver
  18451.            dw      MediaChk        ; 1  = media check on block device
  18452.            dw      BuildBPB        ; 2  = build BIOS parameter block
  18453.            dw      Error           ; 3  = reserved
  18454.            dw      Read            ; 4  = read (input) from device
  18455.            dw      Error           ; 5  = nondestructive read
  18456.            dw      Error           ; 6  = return input status
  18457.            dw      Error           ; 7  = flush device input buffers
  18458.            dw      Write           ; 8  = write (output) to device
  18459.            dw      Write           ; 9  = write with verify
  18460.            dw      Error           ; 10 = return output status
  18461.            dw      Error           ; 11 = flush output buffers
  18462.            dw      Error           ; 12 = reserved
  18463.            dw      Error           ; 13 = device open
  18464.            dw      Error           ; 14 = device close
  18465.            dw      Error           ; 15 = removable media
  18466.            dw      GenIOCTL        ; 16 = generic IOCTL
  18467.            dw      Error           ; 17 = reset media
  18468.            dw      GSLogDrv        ; 18 = get logical drive
  18469.            dw      GSLogDrv        ; 19 = set logical drive
  18470.            dw      Error           ; 20 = deinstall
  18471.            dw      Error           ; 21 = reserved
  18472.            dw      Error           ; 22 = partitionable fixed disks
  18473.            dw      Error           ; 23 = get fixed disk unit map
  18474.            dw      Error           ; 24 = reserved
  18475.            dw      Error           ; 25 = reserved
  18476.            dw      Error           ; 26 = reserved
  18477.  
  18478.                                    ; start of data discarded
  18479.                                    ; after initialization
  18480.  
  18481.    ident   db      cr,lf,lf        ; successful installation message
  18482.            db      'TINYDISK 64 KB RAM disk for OS/2'
  18483.            db      cr,lf
  18484.            db      'RAM disk will be drive '
  18485.    drive   db      'X:'
  18486.            db      cr,lf
  18487.    ident_len equ $-ident
  18488.  
  18489.    abort   db      cr,lf           ; aborted installation message
  18490.            db      'TINYDISK installation aborted'
  18491.            db      cr,lf
  18492.    abort_len equ $-abort
  18493.  
  18494.    volname db      'TINYDISK   '   ; volume label for RAM disk
  18495.            db      08h             ; attribute byte
  18496.            db      10 dup (0)      ; reserved area
  18497.            dw      0               ; time = 00:00
  18498.            dw      1021h           ; date = January 1, 1988
  18499.            db      6 dup (0)       ; reserved area
  18500.    volname_len equ $-volname
  18501.  
  18502.    _DATA   ends
  18503.  
  18504.  
  18505.    _TEXT   segment word public 'CODE'
  18506.  
  18507.            assume  cs:_TEXT,ds:DGROUP,es:NOTHING
  18508.  
  18509.    Strat   proc    far             ; Strategy entry point
  18510.                                    ; ES:BX = request packet
  18511.  
  18512.            mov     di,es:[bx+2]    ; get command code from packet
  18513.            and     di,0ffh
  18514.            cmp     di,maxcmd       ; supported by this driver?
  18515.            jle     Strat1          ; jump if command code OK
  18516.  
  18517.            call    Error           ; bad command code
  18518.            jmp     Strat2
  18519.  
  18520.    Strat1: add     di,di           ; go to command code routine
  18521.            call    word ptr [di+dispch]
  18522.  
  18523.    Strat2:                         ; return with AX = status
  18524.            mov     es:[bx+3],ax    ; put status in request packet
  18525.            ret                     ; return to OS/2 kernel
  18526.  
  18527.    Strat   endp
  18528.    ; Command code routines are called by the Strategy routine
  18529.    ; via the Dispatch table with ES:BX pointing to the request
  18530.    ; header.  Each routine should return ES:BX unchanged
  18531.    ; and AX = status to be placed in request packet:
  18532.    ; 0100H if 'done' and no error
  18533.    ; 0000H if thread should block pending interrupt
  18534.    ; 81xxH if 'done' and error detected (xx = error code)
  18535.  
  18536.  
  18537.    MediaChk proc   near            ; function 1 = media check
  18538.  
  18539.                                    ; return 'not changed' code
  18540.            mov     byte ptr es:[bx+0eh],1
  18541.  
  18542.            mov     ax,0100h        ; return 'done' status
  18543.            ret
  18544.  
  18545.    MediaChk endp
  18546.  
  18547.  
  18548.    BuildBPB proc   near            ; function 2 = build BPB
  18549.  
  18550.                                    ; put BPB address into
  18551.                                    ; request packet
  18552.            mov     word ptr es:[bx+12h],offset DGROUP:bpb
  18553.            mov     word ptr es:[bx+14h],ds
  18554.  
  18555.            mov     ax,0100h        ; return 'done' status
  18556.            ret
  18557.  
  18558.    BuildBPB endp
  18559.  
  18560.  
  18561.    Read    proc    near            ; function 4 = read
  18562.  
  18563.            push    es              ; save request packet address
  18564.            push    bx
  18565.  
  18566.            call    setup           ; set up transfer variables
  18567.  
  18568.    Read1:  mov     ax,xfrcnt       ; done with all sectors yet?
  18569.            cmp     ax,xfrreq
  18570.            je      read2           ; jump if transfer completed
  18571.  
  18572.            mov     ax,ds           ; set ES = DGROUP
  18573.            mov     es,ax
  18574.            mov     ax,xfrsec       ; get sector number
  18575.            call    MapDS           ; and map it to DS:SI
  18576.                                    ; (may force mode switch)
  18577.  
  18578.            push    es              ; save DGROUP selector
  18579.  
  18580.                                    ; convert destination physical
  18581.                                    ; address to virtual address...
  18582.            mov     bx,word ptr es:xfraddr
  18583.            mov     ax,word ptr es:xfraddr+2
  18584.            mov     cx,secsize      ; segment length
  18585.            mov     dh,1            ; leave result in ES:DI
  18586.            mov     dl,PhysToVirt   ; function number
  18587.            call    es:devhlp       ; transfer to kernel
  18588.  
  18589.            mov     cx,secsize      ; transfer logical sector from
  18590.            cld                     ; RAM disk to requestor
  18591.            rep movsb
  18592.  
  18593.            pop     ds              ; restore DGROUP addressing
  18594.  
  18595.            sti                     ; PhysToVirt may mask interrupt
  18596.  
  18597.            inc     xfrsec          ; advance sector number
  18598.  
  18599.                                    ; advance transfer address
  18600.            add     word ptr xfraddr,secsize
  18601.            adc     word ptr xfraddr+2,0
  18602.  
  18603.            inc     xfrcnt          ; count sectors transferred
  18604.            jmp     read1
  18605.  
  18606.    Read2:                          ; all sectors transferred
  18607.  
  18608.            mov     dl,UnPhysToVirt ; function number
  18609.            call    devhlp          ; transfer to kernel
  18610.  
  18611.            pop     bx              ; restore request packet address
  18612.            pop     es
  18613.  
  18614.            mov     ax,xfrcnt       ; put actual transfer count
  18615.            mov     es:[bx+12h],ax  ; into request packet
  18616.  
  18617.            mov     ax,0100h        ; return 'done' status
  18618.            ret
  18619.  
  18620.    Read    endp
  18621.    Write   proc    near            ; functions 8,9 = write
  18622.  
  18623.            push    es              ; save request packet address
  18624.            push    bx
  18625.  
  18626.            call    setup           ; set up transfer variables
  18627.  
  18628.    Write1: mov     ax,xfrcnt       ; done with all sectors yet?
  18629.            cmp     ax,xfrreq
  18630.            je      write2          ; jump if transfer completed
  18631.  
  18632.            mov     ax,xfrsec       ; get sector number
  18633.            call    MapES           ; map it to ES:DI
  18634.                                    ; (may force mode switch)
  18635.  
  18636.            push    ds              ; save DGROUP selector
  18637.  
  18638.                                    ; convert source physical
  18639.                                    ; address to virtual address...
  18640.            mov     bx,word ptr xfraddr
  18641.            mov     ax,word ptr xfraddr+2
  18642.            mov     cx,secsize      ; segment length
  18643.            mov     dh,0            ; leave result in DS:SI
  18644.            mov     dl,PhysToVirt   ; function number
  18645.            call    devhlp          ; transfer to kernel
  18646.  
  18647.            mov     cx,secsize      ; transfer logical sector from
  18648.            cld                     ; requestor to RAM disk
  18649.            rep movsb
  18650.  
  18651.            pop     ds              ; restore DGROUP addressing
  18652.            sti                     ; PhysToVirt might have masked
  18653.            inc     xfrsec          ; advance sector number
  18654.  
  18655.                                    ; advance transfer address
  18656.            add     word ptr xfraddr,secsize
  18657.            adc     word ptr xfraddr+2,0
  18658.  
  18659.            inc     xfrcnt          ; count sectors transferred
  18660.            jmp     write1
  18661.  
  18662.    Write2:                         ; all sectors transferred
  18663.  
  18664.            mov     dl,UnPhysToVirt ; function number
  18665.            call    devhlp          ; transfer to kernel
  18666.            pop     bx              ; restore request packet address
  18667.            pop     es
  18668.  
  18669.            mov     ax,xfrcnt       ; put actual transfer count
  18670.            mov     es:[bx+12h],ax  ; into request packet
  18671.  
  18672.            mov     ax,0100h        ; return 'done' status
  18673.            ret
  18674.  
  18675.    Write   endp
  18676.  
  18677.  
  18678.    GenIOCTL proc   near            ; function 16 = Generic IOCtl
  18679.  
  18680.            push    es              ; save request packet address
  18681.            push    bx
  18682.  
  18683.            mov     ax,8103h        ; assume unknown command
  18684.  
  18685.                                    ; Get Device Parameters call?
  18686.            cmp     byte ptr es:[bx+0dh],8
  18687.            jne     Gen9            ; no, set 'done' and 'error'
  18688.            cmp     byte ptr es:[bx+0eh],63h
  18689.            jne     Gen9            ; no, set 'done' and 'error'
  18690.  
  18691.                                    ; verify user's access
  18692.            mov     ax,es:[bx+15h]  ; selector
  18693.            mov     di,es:[bx+13h]  ; offset
  18694.            mov     cx,gdprec_len   ; length to be written
  18695.            mov     dh,1            ; need read/write access
  18696.            mov     dl,VerifyAccess ; function number
  18697.            call    devhlp          ; transfer to kernel
  18698.  
  18699.            mov     ax,810ch        ; if no access, exit with
  18700.            jc      Gen9            ; 'done' and general failure
  18701.  
  18702.                                    ; get destination address
  18703.            les     di,dword ptr es:[bx+13h]
  18704.                                    ; copy device info to caller
  18705.            mov     si,offset DGROUP:bpb
  18706.            mov     cx,gdprec_len   ; length of parameter block
  18707.            cld
  18708.            rep movsb
  18709.  
  18710.            mov     ax,0100h        ; set 'done' status
  18711.    Gen9:   pop     bx              ; restore request packet address
  18712.            pop     es
  18713.            ret                     ; and return with status
  18714.  
  18715.    GenIOCTL endp
  18716.  
  18717.  
  18718.    GSLogDrv proc   near            ; function 18, 19 = get
  18719.                                    ; or set logical drive
  18720.  
  18721.                                    ; return code indicating there
  18722.                                    ; are no logical drive aliases
  18723.            mov     byte ptr es:[bx+1],0
  18724.  
  18725.            mov     ax,0100h        ; return 'done' status
  18726.            ret
  18727.  
  18728.    GSLogDrv endp
  18729.  
  18730.  
  18731.    Error   proc    near            ; bad command code
  18732.  
  18733.            mov     ax,8103h        ; error bit and 'done' status
  18734.            ret                     ; and "Unknown Command" code
  18735.  
  18736.    Error   endp
  18737.  
  18738.  
  18739.    MapES   proc    near            ; map sector number to
  18740.                                    ; virtual memory address
  18741.                                    ; call with AX = logical sector
  18742.                                    ; return ES:DI = memory address
  18743.                                    ;        and Carry clear
  18744.                                    ; or     Carry set if error
  18745.  
  18746.            mov     di,secsize      ; bytes per sector
  18747.            mul     di              ; * logical sector number
  18748.            xchg    ax,dx           ; AX:BX := 32-bit physical
  18749.            mov     bx,dx           ; sector address
  18750.            add     bx,word ptr dbase
  18751.            adc     ax,word ptr dbase+2
  18752.  
  18753.            mov     cx,secsize
  18754.            mov     dh,1            ; result to ES:DI
  18755.            mov     dl,PhysToVirt   ; function number
  18756.            call    devhlp          ; transfer to kernel
  18757.            ret                     ; return ES:DI = sector address
  18758.  
  18759.    MapES   endp
  18760.  
  18761.  
  18762.    MapDS   proc    near            ; map sector number to
  18763.                                    ; virtual memory address
  18764.                                    ; call with AX = logical sector
  18765.                                    ; return DS:SI = memory address
  18766.                                    ;        and Carry clear
  18767.                                    ; or     Carry set if error
  18768.  
  18769.            mov     si,secsize      ; bytes per sector
  18770.            mul     si              ; * logical sector number
  18771.            xchg    ax,dx           ; AX:BX := 32-bit physical
  18772.            mov     bx,dx           ; sector address
  18773.            add     bx,word ptr dbase
  18774.            adc     ax,word ptr dbase+2
  18775.  
  18776.            mov     cx,secsize
  18777.            mov     dh,0            ; result to DS:SI
  18778.            mov     dl,PhysToVirt   ; function number
  18779.            call    devhlp          ; transfer to kernel
  18780.  
  18781.            ret                     ; return DS:SI = sector address
  18782.  
  18783.    MapDS   endp
  18784.  
  18785.  
  18786.    Setup   proc    near            ; set up for read/write
  18787.                                    ; ES:BX = request packet
  18788.                                    ; extracts address, start, count
  18789.  
  18790.            mov     ax,es:[bx+14h]  ; starting sector number
  18791.            mov     xfrsec,ax
  18792.  
  18793.            mov     ax,es:[bx+12h]  ; sectors requested
  18794.            mov     xfrreq,ax
  18795.  
  18796.            mov     ax,es:[bx+0eh]  ; requestor's buffer address
  18797.            mov     word ptr xfraddr,ax
  18798.            mov     ax,es:[bx+10h]
  18799.            mov     word ptr xfraddr+2,ax
  18800.  
  18801.            mov     xfrcnt,0        ; initialize sectors
  18802.                                    ; transferred counter
  18803.            ret
  18804.  
  18805.    Setup   endp
  18806.  
  18807.                                    ; start of code discarded
  18808.                                    ; after initialization
  18809.  
  18810.    Init    proc    near            ; function 0 = initialize
  18811.  
  18812.            mov     ax,es:[bx+0eh]  ; get DevHlp entry point
  18813.            mov     word ptr devhlp,ax
  18814.            mov     ax,es:[bx+10h]
  18815.            mov     word ptr devhlp+2,ax
  18816.  
  18817.            mov     al,es:[bx+16h]  ; unit code for this drive
  18818.            add     al,'A'          ; convert to ASCII and place
  18819.            mov     drive,al        ; in output string
  18820.  
  18821.                                    ; display sign-on message...
  18822.            push    stdout          ; handle for standard output
  18823.            push    ds              ; address of message
  18824.            push    offset DGROUP:ident
  18825.            push    ident_len       ; length of message
  18826.            push    ds              ; receives bytes written
  18827.            push    offset DGROUP:wlen
  18828.            call    DosWrite
  18829.  
  18830.                                    ; set offsets to end of code
  18831.                                    ; and data segments
  18832.            mov     word ptr es:[bx+0eh],offset _TEXT:Init
  18833.            mov     word ptr es:[bx+10h],offset DGROUP:ident
  18834.  
  18835.                                    ; logical units supported
  18836.            mov     byte ptr es:[bx+0dh],1
  18837.  
  18838.                                    ; pointer to BPB array
  18839.            mov     word ptr es:[bx+12h],offset DGROUP:array
  18840.            mov     word ptr es:[bx+14h],ds
  18841.  
  18842.            push    es              ; save request packet address
  18843.            push    bx
  18844.  
  18845.                                    ; allocate RAM disk storage
  18846.            mov     bx,0            ; AX:BX = size of allocated
  18847.            mov     ax,1            ; block in bytes
  18848.            mov     dh,0            ; request block above 1 MB
  18849.            mov     dl,AllocPhys    ; function number
  18850.            call    devhlp          ; transfer to kernel
  18851.            jc      Init9           ; jump if allocation failed
  18852.  
  18853.                                    ; save physical address
  18854.                                    ; of allocated block
  18855.            mov     word ptr dbase,bx
  18856.            mov     word ptr dbase+2,ax
  18857.  
  18858.            call    format          ; format the RAM disk
  18859.            jc      Init9           ; jump if format failed
  18860.  
  18861.            pop     bx              ; restore request packet address
  18862.            pop     es
  18863.  
  18864.            mov     ax,0100h        ; return 'done' status
  18865.            ret
  18866.  
  18867.    Init9:                          ; abort driver installation
  18868.  
  18869.            pop     bx              ; restore request packet address
  18870.            pop     es
  18871.  
  18872.                                    ; display installation aborted...
  18873.            push    stdout          ; handle for standard output
  18874.            push    ds              ; address of message
  18875.            push    offset DGROUP:abort
  18876.            push    abort_len       ; length of message
  18877.            push    ds              ; receives bytes written
  18878.            push    offset DGROUP:wlen
  18879.            call    DosWrite        ; transfer to OS/2
  18880.  
  18881.                                    ; set zero units
  18882.            mov     byte ptr es:[bx+0dh],0
  18883.  
  18884.                                    ; set lengths of code and
  18885.                                    ; data segments
  18886.            mov     word ptr es:[bx+0eh],0
  18887.            mov     word ptr es:[bx+10h],0
  18888.  
  18889.            mov     ax,810ch        ; return error and done and
  18890.                                    ; general failure
  18891.            ret
  18892.  
  18893.    Init    endp
  18894.    Format  proc    near            ; format the RAM disk area
  18895.  
  18896.                                    ; calculate length of
  18897.                                    ; RAM disk control areas
  18898.            mov     al,byte ptr bpb+5
  18899.            cbw                     ; number of FATs
  18900.            mov     cx,bpb+0bh      ; * sectors per FAT
  18901.            mul     cx
  18902.            mov     cx,bpb+6        ; entries in root directory
  18903.            shr     cx,4            ; divide by 16 for sectors
  18904.            add     ax,cx           ; (FAT + dir sectors
  18905.            add     ax,bpb+3        ; + reserved sectors)
  18906.            mov     cx,secsize      ; * bytes/sector
  18907.            mul     cx              ; = total length
  18908.            mov     cx,ax
  18909.  
  18910.                                    ; convert RAM disk base to
  18911.                                    ; virtual address
  18912.            mov     bx,word ptr dbase
  18913.            mov     ax,word ptr dbase+2
  18914.            mov     dh,1            ; leave result in ES:DI
  18915.            mov     dl,PhysToVirt   ; function number
  18916.            call    devhlp          ; transfer to kernel
  18917.            jc      Format9         ; jump if error
  18918.  
  18919.            xor     ax,ax           ; now zero control areas
  18920.            cld                     ; (assume CX still = length)
  18921.            rep stosb
  18922.  
  18923.            mov     ax,0            ; get address of logical
  18924.            call    MapES           ; sector zero
  18925.            jc      Format9         ; jump if error
  18926.            mov     si,offset DGROUP:bootrec
  18927.            mov     cx,bootrec_len  ; copy boot record
  18928.            rep movsb               ; to logical sector zero
  18929.  
  18930.            mov     ax,word ptr bpb+3
  18931.            call    MapES           ; get address of FAT sector
  18932.            jc      Format9         ; jump if error
  18933.            mov     al,byte ptr bpb+0ah
  18934.            mov     es:[di],al      ; medium ID byte into FAT
  18935.            mov     word ptr es:[di+1],-1
  18936.            mov     ax,word ptr bpb+3
  18937.            add     ax,word ptr bpb+0bh
  18938.            call    MapES           ; get address of directory
  18939.            jc      Format9         ; jump if error
  18940.            mov     si,offset DGROUP:volname
  18941.            mov     cx,volname_len
  18942.            rep movsb               ; copy volume label to it
  18943.  
  18944.            mov     dl,UnPhysToVirt ; function number
  18945.            call    devhlp          ; transfer to kernel
  18946.  
  18947.            clc                     ; signal format successful
  18948.  
  18949.    Format9:                        ; return with Carry = 0
  18950.            ret                     ; if success, 1 if error
  18951.  
  18952.    Format  endp
  18953.  
  18954.    _TEXT   ends
  18955.  
  18956.            end
  18957.    ──────────────────────────────────────────────────────────────────────────
  18958.  
  18959.    Figure 17-31.  TINYDISK.ASM, the source code for TINYDISK.SYS, an
  18960.    installable OS/2 RAM disk driver.
  18961.  
  18962.    ──────────────────────────────────────────────────────────────────────────
  18963.    LIBRARY TINYDISK
  18964.    PROTMODE
  18965.    ──────────────────────────────────────────────────────────────────────────
  18966.  
  18967.    Figure 17-32.  TINYDISK.DEF, the module definition file used during
  18968.    linking of TINYDISK.SYS.
  18969.  
  18970.    At initialization time, TINYDISK allocates itself 64 KB of fixed memory
  18971.    above the 1 MB boundary for use as RAM disk storage. It then initializes
  18972.    the RAM disk's control areas by clearing the FAT and directory, copying a
  18973.    simulated boot block to the RAM disk's "logical sector 0," placing the
  18974.    fixed disk medium ID byte at the beginning of the FAT, and copying a
  18975.    simulated volume label to the start of the directory. TINYDISK then
  18976.    displays a sign-on message that includes its drive code.
  18977.  
  18978.    When TINYDISK receives a Read or Write request packet from the kernel, it
  18979.    simply translates the sector number into an offset within the RAM disk
  18980.    storage area, calls PhysToVirt to obtain virtual addresses appropriate for
  18981.    the current mode, and copies data directly between the application's
  18982.    buffer and the RAM disk memory. To keep the sample source code to a
  18983.    tolerable length, other request packets (such as Build BPB, Media Check,
  18984.    and Generic IOCtl) are handled in an adequate but simplistic fashion.
  18985.  
  18986.    Note especially the sequence of events in the TINYDISK Read and Write
  18987.    routines. Both routines convert the address of the RAM disk storage buffer
  18988.    first because it always lies above the 1 MB boundary and is guaranteed to
  18989.    cause a switch from real mode to protected mode if a switch is going to
  18990.    occur at all. The segment register that points to DGROUP is not saved on
  18991.    the stack until after the first call to PhysToVirt because the value in
  18992.    the register might change as a side effect of a mode switch. The code in
  18993.    these routines illustrates on a small scale the extreme care with which
  18994.    addressability must be handled in a full-fledged driver.
  18995.  
  18996.    TINYDISK.SYS is built from the source file TINYDISK.ASM and the module
  18997.    definition file TINYDISK.DEF with the following commands:
  18998.  
  18999.    [C:\] MASM TINYDISK.ASM;  <Enter>
  19000.  
  19001.    [C:\] LINK TINYDISK,TINYDISK.SYS,,OS2,TINYDISK  <Enter>
  19002.  
  19003.    After assembling and linking the driver, copy the file TINYDISK.SYS to the
  19004.    root directory of your boot disk, add the line DEVICE=TINYDISK.SYS to your
  19005.    CONFIG.SYS file, and restart the system. During system initialization you
  19006.    will see a line such as the following:
  19007.  
  19008.    TINYDISK 64 KB RAM disk for OS/2
  19009.    RAM disk will be drive F:
  19010.  
  19011.    You can then copy files to and from the TINYDISK drive as though it were a
  19012.    small but very fast fixed disk. If TINYDISK cannot install itself because
  19013.    memory proves insufficient or because another system error occurs, it
  19014.    issues the following error message:
  19015.  
  19016.    TINYDISK installation aborted
  19017.  
  19018.  
  19019.  
  19020.  ────────────────────────────────────────────────────────────────────────────
  19021.  Chapter 18  Device Monitors
  19022.  
  19023.    Ordinarily, the flow of data between a physical character device and an
  19024.    application program follows a well-defined path through the system (Figure
  19025.    18-1). By the time a character reaches an application program, it has
  19026.    been filtered, buffered, and possibly translated in two or more layers of
  19027.    system software. The unprocessed data that arrives from the device is
  19028.    hidden from normal applications by the privilege levels and protection
  19029.    mechanisms of the 80286 protected mode.
  19030.  
  19031.                     ┌─────────────────────┐
  19032.                     │    Presentation     │
  19033.                     │ Manager application │
  19034.                     └───────────┬────────┘
  19035.                             │    │
  19036.    ┌─────────────┐   ┌──────┴───────────┐
  19037.    │   Kernel    │   │ OS/2 Presentation │
  19038.    │ application │   │      Manager      │
  19039.    └────────┬───┘   └──────────┬───────┘
  19040.         │    │              │    │
  19041.    ┌────┴──────────────────┴───────────┐
  19042.    │             OS/2 kernel             │
  19043.    └──────┬─────────────────────────────┘
  19044.       │    │
  19045.    ┌──┴──────┐
  19046.    │  Device  │
  19047.    │  driver  │
  19048.    └──────┬──┘
  19049.       │    │
  19050.    ┌──┴──────┐
  19051.    │ Physical │
  19052.    │  device  │
  19053.    └──────────┘
  19054.  
  19055.    Figure 18-1.  Normal flow of data between a character device and an
  19056.    application.
  19057.  
  19058.    To let normal programs intercept characters at a low level without running
  19059.    afoul of protected mode restrictions, OS/2 recognizes a class of
  19060.    applications called device monitors, for which it exports a special set of
  19061.    API support functions (Figure 18-2). In effect, these API functions allow
  19062.    an application to insert a probe into a device driver's raw data stream. A
  19063.    device monitor can add, consume, or remap characters or "events" before
  19064.    they pass from the driver to the physical device or from the driver to the
  19065.    kernel or its subsystems for distribution to applications.
  19066.  
  19067.    Function                  Description
  19068.    ──────────────────────────────────────────────────────────────────────────
  19069.    DosMonOpen               Creates connection between device monitor and
  19070.                              character device driver
  19071.  
  19072.    DosMonReg                Inserts application buffers into monitor buffer
  19073.                              chain
  19074.  
  19075.    DosMonRead               Obtains data packet from monitor chain
  19076.  
  19077.    DosMonWrite              Propels data packet into monitor chain
  19078.  
  19079.    DosMonClose              Terminates connection between device monitor and
  19080.                              character device driver
  19081.    ──────────────────────────────────────────────────────────────────────────
  19082.  
  19083.    Figure 18-2.  OS/2 device monitor functions at a glance.
  19084.  
  19085.    Print spoolers, keyboard enhancers, and similar utilities can easily be
  19086.    written as device monitors, eliminating the need for custom device drivers
  19087.    in many cases. Furthermore, multiple applications can register as monitors
  19088.    for the same device and can even specify where they want to be in the
  19089.    chain of all monitors. Similarly, a single monitor can register with
  19090.    multiple device drivers or register multiple times with the same driver.
  19091.    Because monitors (like other applications) run at the lowest privilege
  19092.    level, and because the usual memory protection mechanisms are always in
  19093.    effect, the chances of harmful or unexpected interactions between monitors
  19094.    are minimal.
  19095.  
  19096.    As you might expect, OS/2's support for monitors involves an elaborate
  19097.    dialogue between the application, kernel, and device driver and requires a
  19098.    high degree of cooperation among the three to preserve the system's
  19099.    performance. A special kernel module, called the monitor dispatcher,
  19100.    arbitrates among the three components and moves data from the driver
  19101.    through the buffers of one or more monitors and back to the driver (Figure
  19102.    18-3).
  19103.  
  19104.                     ┌─────────────────────┐
  19105.                     │    Presentation     │
  19106.                     │ Manager application │
  19107.                     └───────────┬────────┘
  19108.                             │    │
  19109.    ┌─────────────┐   ┌──────┴───────────┐   ┌─────────┐
  19110.    │   Kernel    │   │ OS/2 Presentation │   │ Device  │
  19111.    │ application │   │      Manager      │   │ monitor │
  19112.    └────────┬───┘   └──────────┬───────┘   └──────┬─┘
  19113.         │    │              │    │              │    │
  19114.    ┌────┴──────────────────┴───────────┬──────┴────────────┐
  19115.    │             OS/2 kernel             │ Monitor dispatcher │
  19116.    └──────┬─────────────────────────────┴──────┬────────────┘
  19117.       │    │                                    │    │
  19118.    ┌──┴──────┐                                 │    │
  19119.    │  Device  │────────────────────────────────┘    │
  19120.    │  driver  ├──────────────────────────────────────┘
  19121.    └──────┬──┘
  19122.       │    │
  19123.    ┌──┴──────┐
  19124.    │ Physical │
  19125.    │  device  │
  19126.    └──────────┘
  19127.  
  19128.    Figure 18-3.  Flow of data between a character device and an application
  19129.    when a device monitor is registered. A monitor runs as a normal "user"
  19130.    process and has access to all the usual OS/2 API services. The characters
  19131.    are passed in packets from the driver to the device monitor, which can
  19132.    translate, delete, or add characters. The characters are then passed back
  19133.    to the driver, and from there they flow through the operating system to
  19134.    application programs or from the driver to the device.
  19135.  
  19136.    OS/2's default KBD$, MOUSE$, and PRN drivers all contain the necessary
  19137.    logic to support device monitors; its COMx and SCREEN$ drivers do not.
  19138.    Monitor support in other character drivers is optional. If you are using a
  19139.    custom device driver from a software or hardware vendor other than
  19140.    Microsoft, refer to the documentation for that driver to determine whether
  19141.    it supports monitors and, if so, what format its monitor data packets
  19142.    assume. An application can determine at run time whether a particular
  19143.    driver supports monitors by calling DosDevIOCtl Category 11 Function 60H
  19144.    (Query Monitor Support).
  19145.  
  19146.  
  19147.  Device Monitor Organization
  19148.  
  19149.    Device monitoring is restricted to protected mode applications; it is not
  19150.    available to Family or real mode programs. To register as a device
  19151.    monitor, an application first calls the DosMonOpen function with the
  19152.    logical name of the character device you want it to tap. If the driver for
  19153.    that device supports monitors, DosMonOpen returns a monitor handle.
  19154.  
  19155.    The application then calls DosMonReg with the monitor handle and the
  19156.    addresses of two data buffers that will be used for input and output of
  19157.    device driver data packets. A suitable buffer must be at least 20 bytes
  19158.    longer than the monitor data packet used by the character device driver of
  19159.    interest. Additional parameters to the DosMonReg call are an index (which
  19160.    identifies the screen group for keyboard and mouse monitors) and a code
  19161.    that specifies the location you prefer for this program in the chain of
  19162.    all monitors attached to this particular driver: first, last, or no
  19163.    preference. If additional programs later request the first (or last)
  19164.    position, the first program to make the request retains first (or last)
  19165.    position in the chain, the second program comes next, and so on.
  19166.  
  19167.    If the DosMonReg call succeeds, the program has successfully registered as
  19168.    a device monitor and becomes responsible for passing the driver's data
  19169.    through its monitor buffers without impeding the performance of the
  19170.    driver. To do so, it calls DosMonRead to obtain a data packet from the
  19171.    monitor chain and then DosMonWrite to return a packet to the chain.
  19172.    Typically, the program dedicates a thread with time-critical priority to a
  19173.    tight loop that performs no API calls except for DosSemSet, DosSemClear,
  19174.    and successive invocations of DosMonRead and DosMonWrite, so that other
  19175.    I/O operations or lengthy calculations by the monitor do not interfere
  19176.    with the driver's throughput.
  19177.  
  19178.    Between each DosMonRead and DosMonWrite, the monitor can inspect the
  19179.    character data. It can translate characters by modifying the packets,
  19180.    consume characters by simply not calling DosMonWrite, or add characters to
  19181.    the data stream by constructing additional packets in the existing format
  19182.    and calling DosMonWrite to shove them into the monitor chain.
  19183.  
  19184.    When the program wants to unhook from the character stream it is
  19185.    monitoring, it calls DosMonClose with the monitor handle it received from
  19186.    the original DosMonOpen. The program can then release its other resources
  19187.    and terminate without any untoward effects. Figure 18-4 shows a skeleton
  19188.    of the code you would use in a monitor.
  19189.  
  19190.    The format of a monitor data packet is unique to the character device
  19191.    driver. The packet usually contains a date and time stamp, a byte of
  19192.    monitor flags, and one or more characters. Figures 18-5 through 18-8 on
  19193.    pp. 425─27 show the structure of the data packets used by OS/2's KBD$,
  19194.    MOUSE$, and PRN drivers.
  19195.  
  19196.    ──────────────────────────────────────────────────────────────────────────
  19197.    KbdName db      'KBD$',0        ; Kbd logical device name
  19198.    KbdHan  dw      ?               ; handle from DosMonOpen
  19199.  
  19200.                                    ; buffers for Kbd monitor
  19201.    KbdIn   dw      128,64 dup (0)  ; monitor input buffer
  19202.    KbdOut  dw      128,64 dup (0)  ; monitor output buffer
  19203.  
  19204.    KbdPkt  db      128 dup (0)     ; receives Kbd data packet
  19205.    KbdPLen dw      ?               ; length of data in KbdPkt
  19206.  
  19207.    ScrGrp  dw      ?               ; current screen group
  19208.  
  19209.            .
  19210.            .
  19211.            .
  19212.                                    ; open monitor connection...
  19213.            push    ds              ; address of device name
  19214.            push    offset DGROUP:KbdName
  19215.            push    ds              ; receives monitor handle
  19216.            push    offset DGROUP:KbdHan
  19217.            call    DosMonOpen      ; transfer to OS/2
  19218.            or      ax,ax           ; was open successful?
  19219.            jnz     error           ; jump if function failed
  19220.  
  19221.                                    ; register Kbd monitor...
  19222.            push    KbdHan          ; handle from DosMonOpen
  19223.            push    ds              ; input buffer address
  19224.            push    offset DGROUP:KbdIn
  19225.            push    ds              ; output buffer address
  19226.            push    offset DGROUP:KbdOut
  19227.            push    1               ; request front of list
  19228.            push    ScrGrp          ; push screen group number
  19229.            call    DosMonReg       ; transfer to OS/2
  19230.            or      ax,ax           ; registration successful?
  19231.            jnz     error           ; jump if function failed
  19232.            .
  19233.            .
  19234.            .
  19235.    next:                           ; this loop is the actual
  19236.                                    ; keyboard monitor...
  19237.  
  19238.                                    ; set max length for read
  19239.            mov     KbdPLen,KbdPLen-KbdPkt
  19240.                                    ; get next Kbd packet...
  19241.            push    ds              ; address of input buffer
  19242.            push    offset DGROUP:KbdIn
  19243.            push    0               ; wait until data available
  19244.            push    ds              ; receives data packet
  19245.            push    offset DGROUP:KbdPkt
  19246.            push    ds              ; receives packet length
  19247.            push    offset DGROUP:KbdPLen
  19248.            call    DosMonRead      ; transfer to OS/2
  19249.            or      ax,ax           ; was read successful?
  19250.            jnz     error           ; jump if function failed
  19251.  
  19252.                                    ; is the key our hot key?
  19253.            cmp     byte ptr KbdPkt+3,HotKey
  19254.            jz      keyact          ; jump if it is
  19255.  
  19256.                                    ; not hot key, pass it on...
  19257.            push    ds              ; output buffer address
  19258.            push    offset DGROUP:KbdOut
  19259.            push    ds              ; contains data packet
  19260.            push    offset DGROUP:KbdPkt
  19261.            push    KbdPLen         ; contains packet length
  19262.            call    DosMonWrite     ; transfer to OS/2
  19263.            or      ax,ax           ; was write successful?
  19264.            jnz     error           ; jump if function failed
  19265.  
  19266.            jmp     next            ; wait for another key
  19267.            .
  19268.            .
  19269.            .
  19270.    done:                           ; cease monitoring...
  19271.            push    KbdHan          ; monitor handle
  19272.            call    DosMonClose     ; transfer to OS/2
  19273.            or      ax,ax           ; was close successful?
  19274.            jnz     error           ; jump if function failed
  19275.            .
  19276.            .
  19277.            .
  19278.    ──────────────────────────────────────────────────────────────────────────
  19279.  
  19280.    Figure 18-4.  Skeleton of application code related to function as a
  19281.    keyboard device monitor.
  19282.  
  19283.        00H ┌──────────────────────┐
  19284.    ───────┤    Monitor flags     │
  19285.        01H ├──────────────────────┤
  19286.    ───────┤  Original scan code  │
  19287.        02H ├──────────────────────┤
  19288.    ───────┤ Translated character │
  19289.        03H ├──────────────────────┤
  19290.    ───────┤ Translated scan code │
  19291.        04H ├──────────────────────┤
  19292.            │     DBCS status      ├───────
  19293.        05H ├──────────────────────┤
  19294.            │      DBCS shift      ├───────
  19295.        06H ├──────────────────────┤
  19296.    ───────┤     Shift state      │
  19297.        08H ├──────────────────────┤
  19298.            │                      │
  19299.            │      Time stamp      ├───────
  19300.            │                      │
  19301.        0CH ├──────────────────────┤
  19302.            │   Keyboard driver    │
  19303.            │      flag word       ├───────
  19304.        0EN └──────────────────────┘
  19305.  
  19306.    Figure 18-5.  Monitor data packet for the KBD$ driver. Note that if bit 6
  19307.    of teh status byte is clear, no character was returned, and the record may
  19308.    reflect changed shift state.
  19309.  
  19310.    00H ┌────────────────────────┐
  19311.        │     Monitor flags      ├────────
  19312.    01H ├────────────────────────┤
  19313.        │        Reserved        │
  19314.    02H ├────────────────────────┤
  19315.        │   Mouse event flags    ├────────
  19316.    04H ├────────────────────────┤
  19317.        │ Mouse event time stamp ├────────
  19318.    08H ├────────────────────────┤
  19319.        │      y coordinate      ├────────
  19320.    0AH ├────────────────────────┤
  19321.        │      x coordinate      ├────────
  19322.    0CH └────────────────────────┘
  19323.  
  19324.    Figure 18-6.  Monitor data packet for the MOUSE$ driver.
  19325.  
  19326.    00H ┌────────────────────────┐
  19327.        │     Monitor flags      ├────────
  19328.    01H ├────────────────────────┤
  19329.        │  Printer driver flags  ├────────
  19330.    02H ├────────────────────────┤
  19331.        │       Process ID       ├────────
  19332.    04H ├────────────────────────┤
  19333.        │                        │
  19334.       ─┴─    Character data    ─┴─
  19335.       ─┬─  (up to 128 bytes)   ─┬─
  19336.        │                        │
  19337.        └────────────────────────┘
  19338.  
  19339.    Figure 18-7.  Monitor character data packet for the PRN driver (maximum
  19340.    length 132 bytes). The application determines the length of the packet
  19341.    from the value returned by DosMonRead.
  19342.  
  19343.       00H ┌────────────────────────┐
  19344.    ──────┤     Monitor flags      │
  19345.       01H ├────────────────────────┤
  19346.    ──────┤  Printer driver flags  │
  19347.       02H ├────────────────────────┤
  19348.           │       Process ID       ├──────
  19349.       04H ├────────────────────────┤
  19350.           │      Command byte      ├──────
  19351.       05H ├────────────────────────┤
  19352.           │      Reserved (0)      │
  19353.       08H ├────────────────────────┤
  19354.    ──────┤    Return code area
  19355.       │
  19356.       0AH ├────────────────────────┤
  19357.           │       Code page        ├──────
  19358.       0CH ├────────────────────────┤
  19359.           │        Font ID         ├──────
  19360.       0EH └────────────────────────┘
  19361.  
  19362.    Figure 18-8.  Monitor code page packet for the PRN driver.
  19363.  
  19364.  
  19365.  Device Driver Support for Monitors
  19366.  
  19367.    A driver informs the kernel that it is prepared to support monitors by
  19368.    issuing the DevHlp call MonitorCreate, which establishes an initial "empty
  19369.    chain" of device monitors and assigns a monitor handle to it. The driver
  19370.    can create the monitor chain during its initialization, or it can wait
  19371.    until the chain is needed.
  19372.  
  19373.    When an application executes DosMonOpen, the driver receives a special
  19374.    Device Open request packet that has bit 3 of the status word set. At this
  19375.    point, the driver must create the empty monitor chain if it hasn't already
  19376.    done so.
  19377.  
  19378.    The driver is informed that an application has issued DosMonReg by the
  19379.    receipt of a Generic IOCtl Category 10 Function 40H request packet; it
  19380.    then calls the DevHlp service Register, which causes the kernel's monitor
  19381.    dispatcher to insert the application's buffers into the monitor chain at
  19382.    the appropriate point. The driver can identify the requesting process by
  19383.    getting the selector for the local information segment with the DevHlp
  19384.    GetDOSVar and then extracting the PID from that segment.
  19385.  
  19386.    After it creates the monitor chain (whether any monitors have registered
  19387.    or not), the driver must pass each character it receives from the device
  19388.    through the chain by calling the DevHlp service MonWrite. The kernel's
  19389.    monitor dispatcher calls a notification routine within the driver when
  19390.    data emerges from the end of the monitor chain. For the mouse and keyboard
  19391.    drivers, the data is then passed onward to the Kbd or Mou subsystems in
  19392.    the usual fashion. In the case of the printer driver, the data is then
  19393.    sent to the parallel port.
  19394.  
  19395.    When an application issues DosMonClose, the driver receives a special
  19396.    Device Close request packet with bit 3 of the status word set. The driver
  19397.    can obtain the PID of the current process by the procedure previously
  19398.    described and then call the DevHlp service DeRegister to remove the
  19399.    application's buffers from the monitor chain. When the driver notes that
  19400.    all monitor connections have been closed, it can call the DevHlp
  19401.    MonitorCreate again with a parameter that causes the monitor chain to be
  19402.    destroyed.
  19403.  
  19404.  
  19405.  Sample Device Monitor: SNAP
  19406.  
  19407.    The listings SNAP.ASM (Figure 18-9) and SNAP.C (Figure 18-10 on page
  19408.    445) contain the source code for SNAP.EXE, a sample OS/2 device monitor.
  19409.    After you launch SNAP from the command prompt, it registers itself as a
  19410.    monitor with the keyboard driver (KBD$) for the current screen group. It
  19411.    then watches the keyboard data stream and captures the current screen
  19412.    display into a disk file whenever it detects a particular hot key. The
  19413.    file is named SNAPxx.IMG (where xx is the current number of the current
  19414.    screen group) and is always located in the root directory of the drive
  19415.    that was current when SNAP was loaded. The user can capture multiple
  19416.    screen dumps into the same file; SNAP separates them with divider lines.
  19417.    If a file with the same name exists when SNAP is loaded, it will be
  19418.    overwritten with new dump data.
  19419.  
  19420.    Although the SNAP program is relatively short, it is fairly complex and
  19421.    illustrates many of the OS/2 programming topics that have been discussed
  19422.    in this book:
  19423.  
  19424.    ■  Reading and writing the video display with the Vio subsystem calls
  19425.  
  19426.    ■  Creating, opening, and updating disk files
  19427.  
  19428.    ■  Generating tones with DosBeep
  19429.  
  19430.    ■  Creating and using RAM and system semaphores for intraprocess and
  19431.       interprocess signaling
  19432.  
  19433.    ■  Creating multiple threads within a process and controlling one thread
  19434.       with another
  19435.  
  19436.    ■  Spawning a child process
  19437.  
  19438.    ■  Registering and deregistering a keyboard monitor and filtering the
  19439.       keyboard data stream for hot keys
  19440.  
  19441.    ──────────────────────────────────────────────────────────────────────────
  19442.            title  SNAP -- Sample OS/2 Device Monitor
  19443.            page   55,132
  19444.            .286
  19445.  
  19446.    ;
  19447.    ; SNAP.ASM
  19448.    ;
  19449.    ; A sample OS/2 device monitor that captures the current display into
  19450.    ; the file SNAPxx.IMG, where xx is the session number.  SNAP works in
  19451.    ; character mode only and may not be used in a PM window.  The
  19452.    ; following keys are defined as defaults:
  19453.    ;
  19454.    ; Alt-F10   hot key to capture a screen
  19455.    ; Ctrl-F10  hot key to deinstall SNAP.EXE
  19456.    ;
  19457.    ; Assemble with:  C> masm snap.asm;
  19458.    ; Link with:  C> link snap,,,os2,snap
  19459.    ;
  19460.    ; Usage is:  C> snap
  19461.    ;
  19462.    ; Copyright (C) 1988 Ray Duncan
  19463.    ;
  19464.  
  19465.    cr      equ     0dh                     ; ASCII character codes
  19466.    lf      equ     0ah
  19467.  
  19468.                                            ; hot key definitions:
  19469.    snapkey equ     71h                     ; snapshot   Alt-F10
  19470.    exitkey equ     67h                     ; exit       Ctrl-F10
  19471.  
  19472.    stksize equ     2048                    ; stack size for threads
  19473.  
  19474.            extrn   DosAllocSeg:far
  19475.            extrn   DosBeep:far
  19476.            extrn   DosBufReset:far
  19477.            extrn   DosClose:far
  19478.            extrn   DosCloseSem:far
  19479.            extrn   DosCreateSem:far
  19480.            extrn   DosCreateThread:far
  19481.            extrn   DosExecPgm:far
  19482.            extrn   DosExit:far
  19483.            extrn   DosGetInfoSeg:far
  19484.            extrn   DosOpenSem:far
  19485.            extrn   DosMonClose:far
  19486.            extrn   DosMonOpen:far
  19487.            extrn   DosMonRead:far
  19488.            extrn   DosMonReg:far
  19489.            extrn   DosMonWrite:far
  19490.            extrn   DosOpen:far
  19491.            extrn   DosSemClear:far
  19492.            extrn   DosSemSet:far
  19493.            extrn   DosSemWait:far
  19494.            extrn   DosSetPrty:far
  19495.            extrn   DosSleep:far
  19496.            extrn   DosSuspendThread:far
  19497.            extrn   DosWrite:far
  19498.            extrn   VioEndPopUp:far
  19499.            extrn   VioGetMode:far
  19500.            extrn   VioPopUp:far
  19501.            extrn   VioReadCharStr:far
  19502.            extrn   VioWrtCharStr:far
  19503.    jerr    macro   p1,p2,p3                ;; Macro to test return code
  19504.            local   zero                    ;; in AX and jump if nonzero
  19505.            or      ax,ax                   ;; Uses JMP DISP16 to avoid
  19506.            jz      zero                    ;; branch out of range errors
  19507.            mov     dx,offset DGROUP:p2     ;; p2 = message address
  19508.            mov     cx,p3                   ;; p3 = message length
  19509.            jmp     p1                      ;; routine p1 displays message
  19510.    zero:
  19511.            endm
  19512.  
  19513.    DGROUP  group   _DATA
  19514.  
  19515.    _DATA   segment word public 'DATA'
  19516.  
  19517.    exitsem dd      0                       ; semaphore for final exit
  19518.    snapsem dd      0                       ; semaphore for 'snap' thread
  19519.  
  19520.    sname   db      '\SEM\SNAP'             ; system semaphore name
  19521.    sname1  db      'nn.LCK',0
  19522.    shandle dd      0                       ; system semaphore handle
  19523.  
  19524.    pflags  dw      0                       ; VioPopUp flags
  19525.  
  19526.    wlen    dw      ?                       ; receives length written
  19527.    action  dw      ?                       ; receives DosOpen action
  19528.  
  19529.    watchID dw      ?                       ; keyboard thread ID
  19530.    snapID  dw      ?                       ; snapshot thread ID
  19531.  
  19532.    sel     dw      ?                       ; selector from DosAllocSeg
  19533.  
  19534.    kname   db      'KBD$',0                ; keyboard device name
  19535.    khandle dw      0                       ; keyboard monitor handle
  19536.  
  19537.    fname   db      '\SNAP'                 ; name of snapshot file
  19538.    fname1  db      'nn.IMG',0
  19539.    fhandle dw      0                       ; handle for snapshot file
  19540.  
  19541.    scrbuf  db      80 dup (0)              ; receives screen data
  19542.    slen    dw      $-scrbuf                ; length of screen buffer
  19543.  
  19544.    newline db      cr,lf                   ; carriage return-linefeed
  19545.    nl_len  equ     $-newline
  19546.    gseg    dw      ?                       ; global info seg. selector
  19547.    lseg    dw      ?                       ; local info seg. selector
  19548.  
  19549.    obuff   db      64 dup (0)              ; receives name of dynlink
  19550.    obuff_len equ   $-obuff                 ; causing DosExecPgm to fail
  19551.  
  19552.    kbdin   dw      128,64 dup (0)          ; input and output buffers
  19553.    kbdout  dw      128,64 dup (0)          ; for keyboard monitor
  19554.  
  19555.    kbdpkt  db      128 dup (0)             ; keyboard data packet
  19556.    kpktlen dw      ?                       ; length of buffer/packet
  19557.  
  19558.    pname   db      'SNAP.EXE',0            ; child process name
  19559.    retcode dd      0                       ; child process info
  19560.  
  19561.    vioinfo label   byte                    ; receives display mode
  19562.            dw      8                       ; length of structure
  19563.            db      0                       ; display mode type
  19564.            db      0                       ; colors
  19565.    cols    dw      0                       ; number of columns
  19566.    rows    dw      0                       ; number of rows
  19567.  
  19568.    msg1    db      'SNAP utility installed!'
  19569.    msg1_len equ    $-msg1
  19570.  
  19571.    msg2    db      'Alt-F10 to capture screen into file SNAP.IMG,'
  19572.    msg2_len equ    $-msg2
  19573.  
  19574.    msg3    db      'Ctrl-F10 to shut down SNAP.'
  19575.    msg3_len equ    $-msg3
  19576.  
  19577.    msg4    db      'SNAP utility deactivated.'
  19578.    msg4_len equ    $-msg4
  19579.  
  19580.    msg5    db      'Error detected during SNAP installation:'
  19581.    msg5_len equ    $-msg5
  19582.  
  19583.    msg6    db      'Can''t create SNAP system semaphore.'
  19584.    msg6_len equ    $-msg6
  19585.  
  19586.    msg7    db      'Can''t start child copy of SNAP.'
  19587.    msg7_len equ    $-msg7
  19588.  
  19589.    msg8    db      'SNAP is already loaded.'
  19590.    msg8_len equ    $-msg8
  19591.    msg9    db      'Can''t open KBD$ monitor connection.'
  19592.    msg9_len equ    $-msg9
  19593.  
  19594.    msg10   db      'Can''t register as KBD$ monitor.'
  19595.    msg10_len equ   $-msg10
  19596.  
  19597.    msg11   db      'Can''t allocate thread stack.'
  19598.    msg11_len equ   $-msg11
  19599.  
  19600.    msg12   db      'Can''t create keyboard thread.'
  19601.    msg12_len equ   $-msg12
  19602.  
  19603.    msg13   db      'Can''t create snapshot thread.'
  19604.    msg13_len equ   $-msg13
  19605.  
  19606.    msg14   db      'Can''t create snapshot file.'
  19607.    msg14_len equ   $-msg14
  19608.  
  19609.    divider db      79 dup ('-'),cr,lf
  19610.    divider_len equ $-divider
  19611.  
  19612.    _DATA   ends
  19613.  
  19614.    _TEXT   segment word public 'CODE'
  19615.  
  19616.            assume  cs:_TEXT,ds:DGROUP
  19617.  
  19618.    main    proc    far                     ; entry point from OS/2
  19619.  
  19620.                                            ; get info segment selectors
  19621.            push    ds                      ; receives global info selector
  19622.            push    offset DGROUP:gseg
  19623.            push    ds                      ; receives local info selector
  19624.            push    offset DGROUP:lseg
  19625.            call    DosGetInfoSeg           ; transfer to OS/2
  19626.  
  19627.                                            ; build system semaphore
  19628.                                            ; and snapshot file names
  19629.            mov     es,gseg                 ; get foreground screen group
  19630.            mov     al,es:[0018h]
  19631.            aam                             ; convert to ASCII
  19632.            add     ax,'00'
  19633.            xchg    ah,al
  19634.            mov     word ptr fname1,ax      ; store into filename
  19635.            mov     word ptr sname1,ax      ; store into semaphore name
  19636.  
  19637.                                            ; does SNAPxx.LCK exist?
  19638.            push    ds                      ; receives semaphore handle
  19639.            push    offset DGROUP:shandle
  19640.            push    ds
  19641.            push    offset DGROUP:sname     ; semaphore name
  19642.            call    DosOpenSem              ; transfer to OS/2
  19643.            or      ax,ax                   ; was open successful?
  19644.            jz      main1                   ; jump, we're child SNAP
  19645.  
  19646.                                            ; we're the parent SNAP,
  19647.                                            ; create system semaphore
  19648.            push    1                       ; make it nonexclusive
  19649.            push    ds                      ; receives semaphore handle
  19650.            push    offset DGROUP:shandle
  19651.            push    ds                      ; system semaphore name
  19652.            push    offset DGROUP:sname
  19653.            call    DosCreateSem            ; transfer to OS/2
  19654.            jerr    error,msg6,msg6_len     ; jump if create failed
  19655.  
  19656.                                            ; set the semaphore...
  19657.            push    word ptr shandle+2      ; semaphore handle
  19658.            push    word ptr shandle
  19659.            call    DosSemSet               ; transfer to OS/2
  19660.  
  19661.                                            ; launch child SNAP...
  19662.            push    ds                      ; object name buffer
  19663.            push    offset DGROUP:obuff     ; receives failed dynlink
  19664.            push    obuff_len               ; length of buffer
  19665.            push    4                       ; child detached
  19666.            push    0                       ; NULL argument pointer
  19667.            push    0
  19668.            push    0                       ; NULL environment pointer
  19669.            push    0
  19670.            push    ds                      ; receives child info
  19671.            push    offset DGROUP:retcode
  19672.            push    ds                      ; pathname for child
  19673.            push    offset DGROUP:pname
  19674.            call    DosExecPgm              ; request launch of child
  19675.            jerr    error,msg7,msg7_len     ; jump if launch failed
  19676.                                            ; wait for child to load
  19677.            push    word ptr shandle+2      ; semaphore handle
  19678.            push    word ptr shandle
  19679.            push    -1                      ; timeout = indefinite
  19680.            push    -1
  19681.            call    DosSemWait              ; transfer to OS/2
  19682.  
  19683.                                            ; close the semaphore...
  19684.            push    word ptr shandle+2      ; semaphore handle
  19685.            push    word ptr shandle
  19686.            call    DosCloseSem             ; transfer to OS/2
  19687.  
  19688.            jmp     main3                   ; now exit
  19689.  
  19690.    main1:                                  ; come here if child SNAP...
  19691.                                            ; check if already resident
  19692.            push    word ptr shandle+2      ; semaphore handle
  19693.            push    word ptr shandle
  19694.            push    0                       ; timeout = 0
  19695.            push    0
  19696.            call    DosSemWait              ; transfer to OS/2
  19697.            or      ax,ax                   ; is semaphore clear?
  19698.            jnz     main2                   ; no, proceed
  19699.  
  19700.                                            ; yes, don't load again
  19701.            mov     dx,offset DGROUP:msg8   ; address of warning message
  19702.            mov     cx,msg8_len             ; length of message
  19703.            jmp     error                   ; display message and exit
  19704.  
  19705.    main2:                                  ; initialize semaphores...
  19706.            push    ds                      ; address of exit semaphore
  19707.            push    offset DGROUP:exitsem
  19708.            call    DosSemSet               ; transfer to OS/2
  19709.  
  19710.            push    ds                      ; address of snapshot semaphore
  19711.            push    offset DGROUP:snapsem
  19712.            call    DosSemSet               ; transfer to OS/2
  19713.  
  19714.                                            ; open monitor connection ...
  19715.            push    ds                      ; address of device name
  19716.            push    offset DGROUP:kname
  19717.            push    ds                      ; receives monitor handle
  19718.            push    offset DGROUP:khandle
  19719.            call    DosMonOpen              ; transfer to OS/2
  19720.            jerr    error,msg9,msg9_len     ; jump if open failed
  19721.                                            ; register as keyboard monitor
  19722.            push    khandle                 ; handle from DosMonOpen
  19723.            push    ds                      ; monitor input buffer address
  19724.            push    offset DGROUP:kbdin
  19725.            push    ds                      ; monitor output buffer address
  19726.            push    offset DGROUP:kbdout
  19727.            push    1                       ; position = front of list
  19728.            mov     es,gseg                 ; foreground session number
  19729.            mov     al,byte ptr es:[0018h]  ; from global info segment
  19730.            xor     ah,ah
  19731.            push    ax
  19732.            call    DosMonReg               ; transfer to OS/2
  19733.            jerr    error,msg10,msg10_len   ; jump if register failed
  19734.  
  19735.            push    stksize                 ; alloc. stack for WATCH thread
  19736.            push    ds                      ; variable to receive selector
  19737.            push    offset DGROUP:sel
  19738.            push    0                       ; not shareable
  19739.            call    DosAllocSeg             ; transfer to OS/2
  19740.            jerr    error,msg11,msg11_len   ; jump, can't allocate stack
  19741.  
  19742.                                            ; create keyboard thread
  19743.            push    cs                      ; initial execution address
  19744.            push    offset _TEXT:watch
  19745.            push    ds                      ; receives thread ID
  19746.            push    offset DGROUP:watchID
  19747.            push    sel                     ; address of thread's stack
  19748.            push    stksize
  19749.            call    DosCreateThread         ; transfer to OS/2
  19750.            jerr    error,msg12,msg12_len   ; jump, can't create thread
  19751.  
  19752.                                            ; promote keyboard thread
  19753.            push    2                       ; scope = single thread
  19754.            push    3                       ; class = time critical
  19755.            push    0                       ; delta = 0
  19756.            push    watchID                 ; thread ID
  19757.            call    DosSetPrty              ; transfer to OS/2
  19758.  
  19759.            push    stksize                 ; allocate stack for SNAP thread
  19760.            push    ds                      ; variable to receive selector
  19761.            push    offset DGROUP:sel
  19762.            push    0                       ; not shareable
  19763.            call    DosAllocSeg             ; transfer to OS/2
  19764.            jerr    error,msg11,msg11_len   ; jump, can't allocate stack
  19765.                                            ; create snapshot thread
  19766.            push    cs                      ; initial execution address
  19767.            push    offset _TEXT:snap
  19768.            push    ds                      ; receives thread ID
  19769.            push    offset DGROUP:snapID
  19770.            push    sel                     ; address of thread's stack
  19771.            push    stksize
  19772.            call    DosCreateThread         ; transfer to OS/2
  19773.            jerr    error,msg13,msg13_len   ; jump, can't create thread
  19774.  
  19775.            call    signon                  ; announce installation
  19776.  
  19777.                                            ; tell parent we are running
  19778.            push    word ptr shandle+2      ; semaphore handle
  19779.            push    word ptr shandle
  19780.            call    DosSemClear             ; transfer to OS/2
  19781.  
  19782.                                            ; block on exit semaphore...
  19783.            push    ds                      ; semaphore handle
  19784.            push    offset DGROUP:exitsem
  19785.            push    -1                      ; timeout = indefinite
  19786.            push    -1
  19787.            call    DosSemWait              ; transfer to OS/2
  19788.  
  19789.            push    watchID                 ; suspend keyboard thread
  19790.            call    DosSuspendThread        ; transfer to OS/2
  19791.  
  19792.            push    snapID                  ; suspend snapshot thread
  19793.            call    DosSuspendThread        ; transfer to OS/2
  19794.  
  19795.                                            ; close monitor connection
  19796.            push    khandle                 ; monitor handle
  19797.            call    DosMonClose             ; transfer to OS/2
  19798.  
  19799.                                            ; close system semaphore
  19800.            push    word ptr shandle+2      ; semaphore handle
  19801.            push    word ptr shandle
  19802.            call    DosCloseSem             ; transfer to OS/2
  19803.  
  19804.                                            ; close snapshot file
  19805.            push    fhandle                 ; file handle
  19806.            call    DosClose                ; transfer to OS/2
  19807.  
  19808.            call    signoff                 ; announce deinstallation
  19809.    main3:  push    1                       ; terminate all threads
  19810.            push    0                       ; return success code
  19811.            call    DosExit                 ; final exit to OS/2
  19812.  
  19813.    main    endp
  19814.  
  19815.    error   proc    near                    ; fatal error encountered
  19816.                                            ; DS:DX = message, CX = length
  19817.  
  19818.            test    khandle,-1              ; monitor active?
  19819.            jz      error1                  ; no, jump
  19820.  
  19821.                                            ; yes, shut it down
  19822.            push    khandle                 ; monitor handle
  19823.            call    DosMonClose             ; transfer to OS/2
  19824.  
  19825.    error1: mov     ax,word ptr shandle     ; system semaphore open?
  19826.            or      ax,word ptr shandle+2
  19827.            jz      error2                  ; no, jump
  19828.  
  19829.                                            ; clear semaphore, in case
  19830.                                            ; we're the child SNAP
  19831.            push    word ptr shandle+2      ; semaphore handle
  19832.            push    word ptr shandle
  19833.            call    DosSemClear             ; transfer to OS/2
  19834.  
  19835.                                            ; close the semaphore
  19836.            push    word ptr shandle+2      ; semaphore handle
  19837.            push    word ptr shandle
  19838.            call    DosCloseSem             ; transfer to OS/2
  19839.  
  19840.    error2: mov     ax,1                    ; get popup window
  19841.            call    popup
  19842.  
  19843.                                            ; display title...
  19844.            push    ds                      ; message address
  19845.            push    offset DGROUP:msg5
  19846.            push    msg5_len                ; message length
  19847.            push    10                      ; Y
  19848.            push    (80-msg5_len)/2         ; X (center it)
  19849.            push    0                       ; Vio handle
  19850.            call    VioWrtCharStr           ; transfer to OS/2
  19851.  
  19852.                                            ; display error message...
  19853.            push    ds                      ; message address
  19854.            push    dx
  19855.            push    cx                      ; message length
  19856.            push    12                      ; Y
  19857.            mov     ax,80                   ; X (center it)
  19858.            sub     ax,cx
  19859.            shr     ax,1
  19860.            push    ax
  19861.            push    0                       ; Vio handle
  19862.            call    VioWrtCharStr           ; transfer to OS/2
  19863.  
  19864.            push    0                       ; pause for 3 seconds
  19865.            push    3000
  19866.            call    DosSleep                ; transfer to OS/2
  19867.  
  19868.            call    unpop                   ; release popup window
  19869.  
  19870.            push    1                       ; terminate all threads
  19871.            push    1                       ; return error code
  19872.            call    DosExit                 ; exit program
  19873.  
  19874.    error   endp
  19875.  
  19876.  
  19877.    watch   proc    far                     ; keyboard thread, monitors
  19878.                                            ; for snapshot or exit hot keys
  19879.  
  19880.            mov     kpktlen,kpktlen-kbdpkt  ; max buffer length for read
  19881.  
  19882.                                            ; get keyboard data packet...
  19883.            push    ds                      ; monitor input buffer address
  19884.            push    offset DGROUP:kbdin
  19885.            push    0                       ; wait until data available
  19886.            push    ds
  19887.            push    offset DGROUP:kbdpkt    ; receives keyboard data packet
  19888.            push    ds
  19889.            push    offset DGROUP:kpktlen   ; contains/receives length
  19890.            call    DosMonRead              ; transfer to OS/2
  19891.  
  19892.            cmp     kbdpkt+2,0              ; is this extended code?
  19893.            jnz     watch1                  ; no, pass it on
  19894.  
  19895.            cmp     kbdpkt+3,exitkey        ; is it exit hot key?
  19896.            jz      watch2                  ; jump if exit key
  19897.  
  19898.            cmp     kbdpkt+3,snapkey        ; is it snapshot hot key?
  19899.            jnz     watch1                  ; no, jump
  19900.            cmp     word ptr kbdpkt+12,0    ; is it break packet?
  19901.            jnz     watch                   ; yes, ignore it
  19902.  
  19903.                                            ; snapshot hot key detected
  19904.                                            ; clear snapshot semaphore...
  19905.            push    ds                      ; semaphore handle
  19906.            push    offset DGROUP:snapsem
  19907.            call    DosSemClear             ; transfer to OS/2
  19908.            jmp     watch                   ; discard this hot key
  19909.  
  19910.    watch1:                                 ; not hot key, pass character
  19911.            push    ds                      ; monitor output buffer address
  19912.            push    offset DGROUP:kbdout
  19913.            push    ds                      ; keyboard data packet address
  19914.            push    offset DGROUP:kbdpkt
  19915.            push    kpktlen                 ; length of data packet
  19916.            call    DosMonWrite             ; transfer to OS/2
  19917.  
  19918.            jmp     watch                   ; get another packet
  19919.  
  19920.    watch2:                                 ; exit hot key detected...
  19921.            cmp     word ptr kbdpkt+12,0    ; is it break packet?
  19922.            jnz     watch                   ; yes, ignore it
  19923.  
  19924.                                            ; clear exit semaphore...
  19925.            push    ds                      ; semaphore handle
  19926.            push    offset DGROUP:exitsem
  19927.            call    DosSemClear             ; transfer to OS/2
  19928.  
  19929.            jmp     watch                   ; let thread 1 shut down
  19930.  
  19931.    watch   endp
  19932.  
  19933.    snap    proc    far                     ; This thread blocks on the
  19934.                                            ; snapshot semaphore, then
  19935.                                            ; dumps the screen contents
  19936.                                            ; to the file SNAPxx.IMG.
  19937.  
  19938.                                            ; open/create snapshot file
  19939.            push    ds                      ; address of filename
  19940.            push    offset DGROUP:fname
  19941.            push    ds                      ; variable to receive file handle
  19942.            push    offset DGROUP:fhandle
  19943.            push    ds                      ; variable to receive action taken
  19944.            push    offset DGROUP:action
  19945.            push    0                       ; initial file size
  19946.            push    0
  19947.            push    0                       ; normal file attribute
  19948.            push    12h                     ; create or replace file
  19949.            push    21h                     ; write access, deny write
  19950.            push    0                       ; DWORD reserved
  19951.            push    0
  19952.            call    DosOpen                 ; transfer to OS/2
  19953.            jerr    error,msg14,msg14_len   ; jump if can't create
  19954.  
  19955.    snap1:                                  ; write divider line
  19956.            push    fhandle                 ; file handle
  19957.            push    ds                      ; address of divider string
  19958.            push    offset DGROUP:divider
  19959.            push    divider_len             ; length of string
  19960.            push    ds                      ; receives bytes written
  19961.            push    offset DGROUP:wlen
  19962.            call    DosWrite                ; transfer to OS/2
  19963.  
  19964.                                            ; force disk update...
  19965.            push    fhandle                 ; file handle
  19966.            call    DosBufReset             ; transfer to OS/2
  19967.  
  19968.                                            ; wait on snapshot semaphore
  19969.            push    ds                      ; semaphore handle
  19970.            push    offset DGROUP:snapsem
  19971.            push    -1                      ; timeout = indefinite
  19972.            push    -1
  19973.            call    DosSemWait              ; transfer to OS/2
  19974.  
  19975.            mov     ax,3                    ; pop-up in transparent mode
  19976.            call    popup                   ; to read screen contents
  19977.  
  19978.                                            ; get screen dimensions...
  19979.            push    ds                      ; receives video mode info
  19980.            push    offset DGROUP:vioinfo
  19981.            push    0                       ; Vio handle
  19982.            call    VioGetMode              ; transfer to OS/2
  19983.  
  19984.            mov     bx,0                    ; BX := initial screen row
  19985.  
  19986.    snap2:                                  ; read line from screen...
  19987.            mov     ax,cols                 ; width to read
  19988.            mov     slen,ax
  19989.            push    ds                      ; address of screen buffer
  19990.            push    offset DGROUP:scrbuf
  19991.            push    ds                      ; contains/receives length
  19992.            push    offset DGROUP:slen
  19993.            push    bx                      ; screen row
  19994.            push    0                       ; screen column
  19995.            push    0                       ; Vio handle
  19996.            call    VioReadCharStr          ; transfer to OS/2
  19997.  
  19998.            push    ds                      ; scan backwards from end
  19999.            pop     es                      ; of line to find last
  20000.            mov     cx,slen                 ; nonblank character
  20001.            mov     di,offset DGROUP:scrbuf
  20002.            add     di,slen
  20003.            dec     di
  20004.            mov     al,20h
  20005.            std
  20006.            repe scasb
  20007.            cld
  20008.            jz      snap3                   ; if Z = True, line was empty
  20009.            inc     cx                      ; otherwise correct the length
  20010.    snap3:                                  ; write line to file...
  20011.            push    fhandle                 ; file handle
  20012.            push    ds                      ; address of data
  20013.            push    offset DGROUP:scrbuf
  20014.            push    cx                      ; clipped line length
  20015.            push    ds                      ; receives bytes written
  20016.            push    offset DGROUP:wlen
  20017.            call    DosWrite                ; transfer to OS/2
  20018.  
  20019.                                            ; write newline (CR-LF)
  20020.            push    fhandle                 ; file handle
  20021.            push    ds
  20022.            push    offset DGROUP:newline   ; address of newline
  20023.            push    nl_len                  ; length of newline
  20024.            push    ds                      ; receives bytes written
  20025.            push    offset DGROUP:wlen
  20026.            call    DosWrite                ; transfer to OS/2
  20027.  
  20028.            inc     bx                      ; bump screen row counter
  20029.            cmp     bx,rows                 ; whole screen done yet?
  20030.            jne     snap2                   ; no, write another
  20031.  
  20032.            push    440                     ; reward user with some
  20033.            push    200                     ; audible feedback
  20034.            call    DosBeep                 ; transfer to OS/2
  20035.            call    unpop                   ; release the screen
  20036.  
  20037.                                            ; done with screen capture,
  20038.                                            ; reset snapshot semaphore
  20039.            push    ds                      ; semaphore handle
  20040.            push    offset DGROUP:snapsem
  20041.            call    DosSemSet               ; transfer to OS/2
  20042.  
  20043.            jmp     snap1                   ; go wait on semaphore
  20044.  
  20045.    snap    endp
  20046.  
  20047.    signon  proc    near                    ; announce installation,
  20048.                                            ; display help message
  20049.  
  20050.            mov     ax,1                    ; put up popup window
  20051.            call    popup                   ; mode = wait, nontransparent
  20052.  
  20053.            push    ds                      ; message address
  20054.            push    offset DGROUP:msg1
  20055.            push    msg1_len                ; message length
  20056.            push    10                      ; Y
  20057.            push    (80-msg1_len)/2         ; X (center it)
  20058.            push    0                       ; Vio handle
  20059.            call    VioWrtCharStr           ; transfer to OS/2
  20060.  
  20061.            push    ds                      ; message address
  20062.            push    offset DGROUP:msg2
  20063.            push    msg2_len                ; message length
  20064.            push    13                      ; Y
  20065.            push    (80-msg2_len)/2         ; X (center it)
  20066.            push    0                       ; Vio handle
  20067.            call    VioWrtCharStr           ; transfer to OS/2
  20068.  
  20069.            push    ds                      ; message address
  20070.            push    offset DGROUP:msg3
  20071.            push    msg3_len                ; message length
  20072.            push    15                      ; Y
  20073.            push    (80-msg3_len)/2         ; X (center it)
  20074.            push    0                       ; Vio handle
  20075.            call    VioWrtCharStr           ; transfer to OS/2
  20076.  
  20077.            push    0                       ; pause for 4 seconds
  20078.            push    4000                    ; so user can read message
  20079.            call    DosSleep                ; transfer to OS/2
  20080.            call    unpop                   ; take down popup window
  20081.            ret                             ; back to caller
  20082.  
  20083.    signon  endp
  20084.  
  20085.    signoff proc    near                    ; announce deinstallation
  20086.  
  20087.            mov     ax,1                    ; put up popup window
  20088.            call    popup                   ; mode = wait, nontransparent
  20089.  
  20090.            push    ds                      ; message address
  20091.            push    offset DGROUP:msg4
  20092.            push    msg4_len                ; message length
  20093.            push    12                      ; Y
  20094.            push    (80-msg4_len)/2         ; X (center it)
  20095.            push    0                       ; Vio handle
  20096.            call    VioWrtCharStr
  20097.  
  20098.            push    0                       ; pause for 2 seconds
  20099.            push    2000                    ; so user can read message
  20100.            call    DosSleep                ; transfer to OS/2
  20101.  
  20102.            call    unpop                   ; take down popup window
  20103.            ret
  20104.  
  20105.    signoff endp
  20106.  
  20107.    popup   proc    near                    ; put up popup window
  20108.                                            ; AX = VioPopUp flags
  20109.                                            ; bit 0 = 0 no wait
  20110.                                            ;         1 wait for pop-up
  20111.                                            ; bit 1 = 0 nontransparent
  20112.                                            ;         1 transparent
  20113.  
  20114.            mov     pflags,ax               ; set pop-up mode
  20115.  
  20116.            push    ds                      ; address of pop-up flags
  20117.            push    offset DGROUP:pflags
  20118.            push    0                       ; Vio handle
  20119.            call    VioPopUp                ; transfer to OS/2
  20120.            ret                             ; back to caller
  20121.  
  20122.    popup   endp
  20123.    unpop   proc    near                    ; take down popup window
  20124.  
  20125.            push    0                       ; Vio handle
  20126.            call    VioEndPopUp             ; transfer to OS/2
  20127.            ret                             ; back to caller
  20128.  
  20129.    unpop   endp
  20130.  
  20131.    _TEXT   ends
  20132.  
  20133.            end     main
  20134.    ──────────────────────────────────────────────────────────────────────────
  20135.  
  20136.    Figure 18-9.  SNAP.ASM, source code for SNAP.EXE sample device monitor.
  20137.  
  20138.    ──────────────────────────────────────────────────────────────────────────
  20139.    /*
  20140.            SNAP.C
  20141.  
  20142.            A sample OS/2 device monitor that captures the current
  20143.            display into the file SNAPxx.IMG, where xx is the
  20144.            session number.  SNAP works in character mode only and
  20145.            and may not be used in a PM window.  The following keys
  20146.            are defined as defaults:
  20147.  
  20148.            Alt-F10   hot key to capture a screen
  20149.            Ctrl-F10  hot key to deinstall SNAP.EXE
  20150.  
  20151.            Compile with:  C> cl /F 2000 snap.c
  20152.  
  20153.            Usage is:  C> snap
  20154.  
  20155.            Copyright (C) 1988 Ray Duncan
  20156.    */
  20157.  
  20158.    #include <stdio.h>
  20159.    #include <string.h>
  20160.  
  20161.                                            /* hot key definitions */
  20162.    #define SNAPKEY  0x71                   /* Alt-F10 to capture screen */
  20163.    #define EXITKEY  0x67                   /* Ctrl-F10 to exit */
  20164.  
  20165.    #define STKSIZE  2048                   /* stack size for threads */
  20166.  
  20167.    #define WAIT     0                      /* parameters for DosMonRead */
  20168.    #define NOWAIT   1                      /* and DosMonWrite */
  20169.    #define API unsigned extern far pascal  /* API function prototypes */
  20170.  
  20171.    API DosBeep(unsigned, unsigned);
  20172.    API DosBufReset(unsigned);
  20173.    API DosClose(unsigned);
  20174.    API DosCloseSem(unsigned long far *);
  20175.    API DosCreateThread(void (far *)(), unsigned far *, void far *);
  20176.    API DosCreateSem(unsigned, unsigned long far *, char far *);
  20177.    API DosExecPgm(char far *, int, int, char far *, char far *,
  20178.                   int far *, char far *);
  20179.    API DosExit(unsigned, unsigned);
  20180.    API DosGetInfoSeg(unsigned far *, unsigned far *);
  20181.    API DosMonClose(unsigned);
  20182.    API DosMonOpen(char far *, unsigned far *);
  20183.    API DosMonRead(void far *, unsigned, char far *, int far *);
  20184.    API DosMonReg(unsigned, void far *, void far *, int, unsigned);
  20185.    API DosMonWrite(void far *, char far *, int);
  20186.    API DosOpen(char far *, unsigned far *, unsigned far *, unsigned long,
  20187.                unsigned, unsigned, unsigned, unsigned long);
  20188.    API DosOpenSem(unsigned long far *, char far *);
  20189.    API DosSemClear(unsigned long far *);
  20190.    API DosSemSet(unsigned long far *);
  20191.    API DosSemWait(unsigned long far *, unsigned long);
  20192.    API DosSetPrty(int, int, int, int);
  20193.    API DosSleep(unsigned long);
  20194.    API DosSuspendThread(unsigned);
  20195.    API DosWrite(unsigned, void far *, int, unsigned far *);
  20196.    API VioEndPopUp(unsigned);
  20197.    API VioGetMode(void far *, unsigned);
  20198.    API VioPopUp(unsigned far *, unsigned);
  20199.    API VioReadCharStr(char far *, int far *, int, int, unsigned);
  20200.    API VioWrtCharStr(char far *, int, int, int, unsigned);
  20201.  
  20202.    void signon(void);                      /* local function prototypes */
  20203.    void signoff(void);
  20204.    void popup(unsigned);
  20205.    void unpop(void);
  20206.    void far snap(void);
  20207.    void far watch(void);
  20208.    void errexit(char *);
  20209.                                            /* RAM semaphores */
  20210.    unsigned long exitsem = 0;              /* exit hot key semaphore */
  20211.    unsigned long snapsem = 0;              /* screen snapshot semaphore */
  20212.  
  20213.    char sname[20];                         /* system semaphore name */
  20214.    unsigned long shandle = 0;              /* system semaphore handle */
  20215.  
  20216.    char fname[20];                         /* snapshot filename */
  20217.    unsigned fhandle = 0;                   /* snapshot file handle */
  20218.  
  20219.    char kname[] = "KBD$";                  /* keyboard device name */
  20220.    unsigned khandle = 0;                   /* keyboard monitor handle */
  20221.  
  20222.    struct _monbuf {                        /* monitor input and */
  20223.        int len;                            /* output buffers */
  20224.        char buf[128];
  20225.        } kbdin  = { sizeof(kbdin.buf)  } ,
  20226.          kbdout = { sizeof(kbdout.buf) } ;
  20227.  
  20228.    struct _vioinfo {                       /* display mode info */
  20229.        int len;
  20230.        char type;
  20231.        char colors;
  20232.        int cols;
  20233.        int rows;
  20234.        } vioinfo;
  20235.  
  20236.    char msg1[] = "SNAP utility installed!";
  20237.    char msg2[] = "Alt-F10 to capture screen image into file SNAP.IMG,";
  20238.    char msg3[] = "Ctrl-F10 to shut down SNAP.";
  20239.    char msg4[] = "SNAP utility deactivated.";
  20240.    char msg5[] = "Error detected during SNAP installation:";
  20241.  
  20242.    main()
  20243.    {
  20244.        char obuff[80];                     /* object name buffer */
  20245.        int retcode[2];                     /* receives child info */
  20246.  
  20247.        unsigned gseg, lseg;                /* receives selectors */
  20248.        char far *ginfo;                    /* global info segment pointer */
  20249.  
  20250.        unsigned snapID, watchID;           /* receives thread IDs */
  20251.        char snapstk[STKSIZE];              /* snapshot thread stack */
  20252.        char watchstk[STKSIZE];             /* keyboard thread stack */
  20253.        DosGetInfoSeg(&gseg, &lseg);        /* get info segment selectors */
  20254.        (long) ginfo = (long) gseg << 16;   /* make far pointer */
  20255.  
  20256.                                            /* build semaphore and filenames */
  20257.        sprintf(sname, "\\SEM\\SNAP%02d.LCK", ginfo[0x18]);
  20258.        sprintf(fname, "\\SNAP%02d.IMG",      ginfo[0x18]);
  20259.  
  20260.        if(DosOpenSem(&shandle, sname))     /* does SEMSNAPxx.LCK exist? */
  20261.        {
  20262.                                            /* no, we're parent SNAP */
  20263.                                            /* create system semaphore */
  20264.            if(DosCreateSem(1, &shandle, sname))
  20265.                errexit("Can't create SNAP system semaphore.");
  20266.            DosSemSet((unsigned long far *) shandle);
  20267.  
  20268.                                            /* start detached child SNAP */
  20269.            if(DosExecPgm(obuff, sizeof(obuff), 4, NULL, NULL, retcode, "snap.e
  20270.                errexit("Can't start child copy of SNAP.");
  20271.  
  20272.                                            /* wait for child to load */
  20273.            DosSemWait((unsigned long far *) shandle, -1L);
  20274.            DosCloseSem((unsigned long far *) shandle);
  20275.        }
  20276.        else                                /* if SNAPxx.LCK exists, */
  20277.        {                                   /* we're the child SNAP */
  20278.  
  20279.                                            /* abort if already resident */
  20280.            if(! DosSemWait((unsigned long far *) shandle, 0L))
  20281.                errexit("SNAP is already loaded.");
  20282.  
  20283.            DosSemSet(&exitsem);            /* initialize exit and */
  20284.            DosSemSet(&snapsem);            /* snapshot semaphores */
  20285.  
  20286.            if(DosMonOpen(kname, &khandle)) /* open monitor connection */
  20287.                errexit("Can't open KBD$ monitor connection.");
  20288.  
  20289.                                            /* register at head of chain */
  20290.            if(DosMonReg(khandle, &kbdin, &kbdout, 1, ginfo[0x18]))
  20291.                errexit("Can't register as KBD$ monitor.");
  20292.  
  20293.                                            /* create keyboard thread */
  20294.            if(DosCreateThread(watch, &watchID, watchstk+STKSIZE))
  20295.                errexit("Can't create keyboard thread.");
  20296.  
  20297.            DosSetPrty(2, 3, 0, watchID);   /* promote keyboard thread */
  20298.                                            /* create snapshot thread */
  20299.            if(DosCreateThread(snap, &snapID, snapstk+STKSIZE))
  20300.                errexit("Can't create snapshot thread.");
  20301.  
  20302.            signon();                       /* announce installation */
  20303.  
  20304.                                            /* tell parent we're running */
  20305.            DosSemClear((unsigned long far *) shandle);
  20306.  
  20307.            DosSemWait(&exitsem, -1L);      /* wait for exit hot key */
  20308.  
  20309.            DosSuspendThread(snapID);       /* suspend snapshot thread */
  20310.            DosSuspendThread(watchID);      /* suspend keyboard thread */
  20311.            DosMonClose(khandle);           /* close monitor connection */
  20312.                                            /* close system semaphore */
  20313.            DosCloseSem((unsigned long far *) shandle);
  20314.            DosClose(fhandle);              /* close snapshot file */
  20315.  
  20316.            signoff();                      /* announce deinstallation */
  20317.        }
  20318.  
  20319.        DosExit(1, 0);                      /* final exit */
  20320.    }
  20321.  
  20322.    /*
  20323.        The 'watch' thread is responsible for monitoring the keyboard
  20324.        data stream.  It clears the 'snapsem' semaphore when the
  20325.        screen capture hot key is detected and clears the 'exitsem'
  20326.        semaphore when the deinstall hot key is detected.
  20327.    */
  20328.    void far watch(void)
  20329.    {
  20330.        char kbdpkt[128];                   /* monitor data packet */
  20331.        int kbdpktlen;                      /* data packet length */
  20332.  
  20333.        while(1)
  20334.        {
  20335.            kbdpktlen = sizeof(kbdpkt);     /* set buffer length */
  20336.  
  20337.                                            /* read monitor data */
  20338.            DosMonRead(&kbdin, WAIT, kbdpkt, &kbdpktlen);
  20339.  
  20340.                                            /* check for hot keys */
  20341.                                            /* ignore key breaks */
  20342.            if((kbdpkt[2] == 0) && (kbdpkt[3] == EXITKEY))
  20343.            {                               /* exit hot key detected */
  20344.                if(kbdpkt[12] == 0) DosSemClear(&exitsem);
  20345.            }
  20346.            else if((kbdpkt[2] == 0) && (kbdpkt[3] == SNAPKEY))
  20347.            {                               /* snapshot hot key detected */
  20348.                if(kbdpkt[12] == 0) DosSemClear(&snapsem);
  20349.            }                               /* not hot key, pass it through */
  20350.            else DosMonWrite(&kbdout, kbdpkt, kbdpktlen);
  20351.        }
  20352.    }
  20353.  
  20354.    /*
  20355.        The 'snap' thread blocks on the 'snapsem' semaphore until it
  20356.        is cleared by the 'watch' thread, then captures the current
  20357.        screen contents into the snapshot file.
  20358.    */
  20359.    void far snap(void)
  20360.    {
  20361.        int i;                              /* scratch variable */
  20362.        unsigned action;                    /* receives DosOpen action */
  20363.        unsigned wlen;                      /* receives DosWrite length */
  20364.        char divider[81];                   /* snapshot divider line */
  20365.        char scrbuf[80];                    /* receives screen data */
  20366.        int slen;                           /* contains buffer size */
  20367.  
  20368.        memset(divider, '-', 79);           /* initialize divider line */
  20369.        divider[79] = 0x0d;
  20370.        divider[80] = 0x0a;
  20371.  
  20372.                                            /* create/replace snapshot file */
  20373.        if(DosOpen(fname, &fhandle, &action, 0L, 0, 0x12, 0x21, 0L))
  20374.            errexit("Can't create snapshot file.");
  20375.  
  20376.        while(1)
  20377.        {                                   /* write divider line */
  20378.            DosWrite(fhandle, divider, sizeof(divider), &wlen);
  20379.            DosBufReset(fhandle);           /* force file update */
  20380.  
  20381.            DosSemWait(&snapsem, -1L);      /* wait for hot key */
  20382.  
  20383.            popup(3);                       /* pop-up in transparent mode */
  20384.            vioinfo.len = sizeof(vioinfo);  /* get screen dimensions */
  20385.            VioGetMode(&vioinfo, 0);
  20386.            for(i = 0; i < vioinfo.rows; i++)
  20387.            {
  20388.                slen = vioinfo.cols;        /* read line from screen */
  20389.                VioReadCharStr(scrbuf, &slen, i, 0, 0);
  20390.  
  20391.                                            /* discard trailing spaces */
  20392.                while((slen > 0) && (scrbuf[slen-1] == 0x20)) slen--;
  20393.  
  20394.                                            /* write line to file */
  20395.                DosWrite(fhandle, scrbuf, slen, &wlen);
  20396.                DosWrite(fhandle, "\x0d\x0a", 2, &wlen);
  20397.            }
  20398.  
  20399.            DosBeep(440, 200);              /* reward the user */
  20400.            unpop();                        /* release screen */
  20401.            DosSemSet(&snapsem);            /* reset snapshot semaphore */
  20402.        }
  20403.    }
  20404.  
  20405.    /*
  20406.        Display the installation and help messages in popup window.
  20407.    */
  20408.    void signon(void)
  20409.    {
  20410.        popup(1);                           /* acquire popup screen */
  20411.        VioWrtCharStr(msg1, sizeof(msg1), 10, ((80-sizeof(msg1))/2), 0);
  20412.        VioWrtCharStr(msg2, sizeof(msg2), 13, ((80-sizeof(msg2))/2), 0);
  20413.        VioWrtCharStr(msg3, sizeof(msg3), 15, ((80-sizeof(msg3))/2), 0);
  20414.        DosSleep(4000L);                    /* pause for 4 seconds */
  20415.        unpop();                            /* release popup screen */
  20416.    }
  20417.  
  20418.    /*
  20419.        Display exit message in popup window.
  20420.    */
  20421.    void signoff(void)
  20422.    {
  20423.        popup(1);                           /* acquire popup screen */
  20424.        VioWrtCharStr(msg4, sizeof(msg4), 12, ((80-sizeof(msg4))/2), 0);
  20425.        DosSleep(2000L);                    /* pause for 2 seconds */
  20426.        unpop();                            /* release popup screen */
  20427.    }
  20428.    /*
  20429.        Get popup screen, using wait/no-wait and
  20430.        transparent/nontransparent flags supplied by caller.
  20431.    */
  20432.    void popup(unsigned pflags)
  20433.    {
  20434.        VioPopUp(&pflags, 0);
  20435.    }
  20436.  
  20437.    /*
  20438.        Take down popup screen.
  20439.    */
  20440.    void unpop(void)
  20441.    {
  20442.        VioEndPopUp(0);
  20443.    }
  20444.  
  20445.    /*
  20446.        Common error exit routine.  Display error message on popup
  20447.        screen and terminate process.
  20448.    */
  20449.    void errexit(char *errmsg)
  20450.    {
  20451.        if(khandle != 0)                    /* close monitor handle */
  20452.            DosMonClose(khandle);           /* if monitor active */
  20453.  
  20454.        if(shandle != 0)                    /* clear and close the */
  20455.        {                                   /* SNAPxx.LCK semaphore */
  20456.            DosSemClear((unsigned long far *) shandle);
  20457.            DosCloseSem((unsigned long far *) shandle);
  20458.        }
  20459.  
  20460.        popup(1);                           /* get popup screen and */
  20461.                                            /* display error message */
  20462.        VioWrtCharStr(msg5, sizeof(msg5), 10, ((80-sizeof(msg5))/2), 0);
  20463.        VioWrtCharStr(errmsg, strlen(errmsg), 12, ((80-strlen(errmsg))/2), 0);
  20464.        DosSleep(3000L);                    /* let user read message */
  20465.        unpop();                            /* release popup screen */
  20466.        DosExit(1, 1);                      /* terminate, exitcode = 1 */
  20467.    }
  20468.    ──────────────────────────────────────────────────────────────────────────
  20469.  
  20470.    Figure 18-10.  SNAP.C, source code for SNAP.EXE sample device monitor.
  20471.  
  20472.  How SNAP Works
  20473.  
  20474.    The SNAP program contains three threads of execution, which communicate
  20475.    using RAM semaphores (Figure 18-11).
  20476.  
  20477.      main thread
  20478.    ┌──────────────┐
  20479.    │  Initialize  │
  20480.    │  semaphores  │
  20481.    └──────┬───────┘
  20482.           │
  20483.    ┌─────────────┐
  20484.    │ Register as  │
  20485.    │ KBD$ monitor │
  20486.    └──────┬───────┘
  20487.           │
  20488.    ┌─────────────┐
  20489.    │ Create watch │
  20490.    │   and snap   │
  20491.    │   threads    │
  20492.    └──────┬───────┘
  20493.           │                      watch thread                   snap thread
  20494.    ┌─────────────┐             ┌────────────┐                ┌──────────────┐
  20495.    │Block on exit │             │ DosMonRead │──────────┐    │    Create    │
  20496.    │  semaphore   │             └─────┬──────┘           │    │   snapshot   │
  20497.    └──────────────┘                   │                  │    │     file     │
  20498.           .                                             │    └──────┬───────┘
  20499.           .                           /\                 │           │
  20500.           .                         /    \               │    ┌─────────────┐
  20501.           .                       /        \             │    │   Block on   │
  20502.           .                Yes  /  Hot key?  \  No       │ ┌─│     snap     │
  20503.    ┌──────────────┐       ┌─────\            /────┐      │ │  │  semaphore   │
  20504.    │Suspend watch │       │       \        /      │      │ │  └──────────────┘
  20505.    │   and snap   │       │         \    /        │      │ │         .
  20506.    │   threads    │       │           \/          │      │ │         .
  20507.    └──────┬───────┘       │                       │      │ │         .
  20508.           │               │             ┌────────────┐  │ │         .
  20509.    ┌─────────────┐       │             │ DosMonWrite ├─│ │         .
  20510.    │Deregister as │       │             └─────────────┘  │ │         .
  20511.    │ KBD$ monitor │    ┌────────────┐                   │ │  ┌──────────────┐
  20512.    └──────┬───────┘    │    Clear    │                   │ │  │ Copy screen  │
  20513.           │            │ appropriate ├───────────────────┘ │  │ to snapshot  │
  20514.    ┌─────────────┐    │  semaphore  │                     │  │     file     │
  20515.    │    Close     │    └─────────────┘                     │  └───────┬──────┘
  20516.    │snapshot file │                                        │          │
  20517.    └──────┬───────┘                                        └──────────┘
  20518.           │
  20519.    ┌─────────────┐
  20520.    │  Terminate   │
  20521.    │   process    │
  20522.    └──────────────┘
  20523.  
  20524.    Figure 18-11.  General flow of control in the SNAP sample device monitor.
  20525.  
  20526.    The primary thread, called main, receives control from OS/2. It calls
  20527.    DosGetInfoSeg to get the selectors for the global and local information
  20528.    segments, extracts the screen group to be monitored from the global
  20529.    information segment, and registers itself as a keyboard monitor using
  20530.    DosMonOpen and DosMonReg. Next, it initializes two RAM semaphores that
  20531.    will be used for signaling: an exit semaphore and a screen snapshot
  20532.    semaphore. The main thread then activates two additional threads (watch
  20533.    and snap), displays the program's sign-on message, and blocks on the exit
  20534.    semaphore.
  20535.  
  20536.    The watch thread simply executes successive calls to DosMonRead and
  20537.    DosMonWrite to pump the keyboard data through its buffers, monitoring the
  20538.    characters that pass for either of the two SNAP hot keys. Use of a
  20539.    dedicated thread for this purpose ensures that keyboard response time is
  20540.    not degraded. If watch detects the screen dump hot key (Alt-F10), it
  20541.    clears the snapshot semaphore to wake up the snap thread. If it sees the
  20542.    deinstall hot key (Ctrl-F10), it triggers the exit semaphore to wake up
  20543.    the main thread.
  20544.  
  20545.    The snap thread creates the file SNAPxx.IMG and then blocks on the
  20546.    snapshot semaphore. Whenever the snapshot semaphore is cleared by the
  20547.    watch thread, the snap thread wakes up, copies the current screen display
  20548.    into SNAPxx.IMG, appends a divider line of hyphens, and forces an update
  20549.    of the file and its directory entry on disk. The snap thread then uses
  20550.    DosBeep to let the user know something happened, resets the snapshot
  20551.    semaphore, and goes back to sleep.
  20552.  
  20553.    The main thread resumes execution only when the watch thread clears the
  20554.    exit semaphore. Upon reawakening it suspends the other two threads and
  20555.    then deregisters the process as a keyboard monitor. Finally, it closes the
  20556.    SNAPxx.IMG file, briefly displays a popup message that the program is
  20557.    removing itself from memory, and then terminates the process.
  20558.  
  20559.    The initial portion of code for the main thread illustrates a useful OS/2
  20560.    trick: placing a process in the background so that another command
  20561.    processor prompt is displayed, without requiring the user to start the
  20562.    program with the DETACH command. SNAP accomplishes this by emulating a
  20563.    UNIX/XENIX "fork" operation.
  20564.  
  20565.    When the main thread first gets control, it tests to see whether a system
  20566.    semaphore with the name \SEM\SNAPxx.LCK exists. If not, it creates and
  20567.    sets the semaphore and then loads and executes another copy of SNAP with
  20568.    the DosExecPgm option 4 for "asynchronous execution, discard return code."
  20569.    The main thread waits for the new child process to clear the system
  20570.    semaphore (indicating that the process has been successfully loaded), and
  20571.    then it terminates.
  20572.  
  20573.    If the main thread finds that the semaphore \SEM\SNAPxx.LCK already
  20574.    exists, it knows itself to be the child copy of SNAP. It clears the
  20575.    semaphore to notify the parent SNAP that it is running and then goes about
  20576.    its business of initializing its data structures and creating additional
  20577.    threads as already described. The child copy of SNAP is free to continue
  20578.    executing in the background; because it was spawned with option 4, CMD.EXE
  20579.    does not wait for it to terminate before issuing another prompt.
  20580.  
  20581.  Building SNAP
  20582.  
  20583.    To assemble the source file SNAP.ASM into the relocatable object module
  20584.    SNAP.OBJ, enter the following command:
  20585.  
  20586.    [C:\] MASM SNAP.ASM;  <Enter>
  20587.  
  20588.    To build the actual program SNAP.EXE, use the Linker to combine the file
  20589.    SNAP.OBJ, the module definition file SNAP.DEF (Figure 18-12), and the
  20590.    dynlink reference file OS2.LIB, as follows:
  20591.  
  20592.    [C:\] LINK SNAP,,,OS2,SNAP  <Enter>
  20593.  
  20594.    To compile the file SNAP.C into the executable file SNAP.EXE, type the
  20595.    following command:
  20596.  
  20597.    [C:\] CL /F 2000  SNAP.C  <Enter>
  20598.  
  20599.    The /F switch allocates an extra-large stack so that stacks for the second
  20600.    and third threads can be allocated out of the main stack.
  20601.  
  20602.    ──────────────────────────────────────────────────────────────────────────
  20603.    NAME SNAP NOTWINDOWCOMPAT
  20604.    PROTMODE
  20605.    STACKSIZE 4096
  20606.    ──────────────────────────────────────────────────────────────────────────
  20607.  
  20608.    Figure 18-12.  SNAP.DEF, module definition file for SNAP.EXE device
  20609.    monitor.
  20610.  
  20611.  Using SNAP
  20612.  
  20613.    You can load SNAP (when any protected mode screen group is selected)
  20614.    simply by entering its name:
  20615.  
  20616.    [C:\] SNAP  <Enter>
  20617.  
  20618.    A popup sign-on message appears briefly to let you know that SNAP is
  20619.    active and to remind you of the hot key assignments. That message is
  20620.    followed by a new prompt from the system's protected mode command
  20621.    interpreter (CMD.EXE).
  20622.  
  20623.    While you are running other programs, SNAP remains resident, waiting to
  20624.    act upon one of two special keycodes. You can request a screen dump at any
  20625.    time by pressing Alt-F10. (A brief tone tells you that SNAP accomplished
  20626.    its task.) To remove SNAP from memory, press Ctrl-F10. You can alter the
  20627.    key assignments by editing the equates at the beginning of SNAP.ASM and
  20628.    reassembling the program.
  20629.  
  20630.    SNAP is active only for the screen group in which it was loaded; it is not
  20631.    aware of keys that are entered when another screen group is brought to the
  20632.    foreground with the Task Manager. You can, however, load a copy of SNAP in
  20633.    each screen group in which you want to use it. The overhead of such
  20634.    additional copies is minuscule because OS/2 allows all the instantiations
  20635.    to share the same machine code segment. With some extra work, you could
  20636.    also modify SNAP so that a single copy registers itself as a device
  20637.    monitor for all the active screen groups in the system.
  20638.  
  20639.  
  20640.  
  20641.  ────────────────────────────────────────────────────────────────────────────
  20642.  Chapter 19  Dynamic Link Libraries
  20643.  
  20644.    Dynamic link libraries (also called dynlink libraries or DLLs) are
  20645.    essentially subroutine packages that are bound to an application at load
  20646.    time or during execution, rather than at link time. But such a terse
  20647.    definition conveys little of the generality and power of this concept. The
  20648.    following list suggests some of the significant benefits of dynlink
  20649.    libraries:
  20650.  
  20651.    ■  Code and read-only data in dynlink libraries is shared invisibly among
  20652.       all the processes ("clients") that use the libraries, a provision which
  20653.       conserves physical memory and disk swap space.
  20654.  
  20655.    ■  Code and data in dynlink libraries can be loaded on demand, so it does
  20656.       not occupy physical memory until it is needed.
  20657.  
  20658.    ■  Dynlink library routines can be referenced symbolically (that is, by
  20659.       name), so a library and the applications that use it can be
  20660.       independently maintained and improved.
  20661.  
  20662.    ■  Centralization of frequently used routines in dynlink libraries reduces
  20663.       the size of each application program file and conserves disk space.
  20664.  
  20665.    ■  Dynlink libraries can contain IOPL segments that enable and disable
  20666.       interrupts and access I/O ports directly. I/O routines for devices that
  20667.       do not require interrupt handlers can be implemented in dynlink
  20668.       libraries rather than in true device drivers; the result, as evidenced
  20669.       by the Vio subsystem, is a much more flexible interface to applications
  20670.       than that provided by a true device driver.
  20671.  
  20672.    Dynamic link libraries were pioneered and proven in real mode Windows, an
  20673.    environment that makes the most of a severely limited address space with a
  20674.    complex software emulation of virtual memory. But in OS/2, which can rely
  20675.    on hardware support for virtual memory and memory protection, dynlink
  20676.    libraries truly come into their own. Dynlink library segments reside in
  20677.    the global memory pool and can be freely moved, swapped, discarded, and
  20678.    reloaded on the basis of overall system activity.
  20679.  
  20680.    In fact, OS/2's dynlink library support can be seen as a general-purpose
  20681.    overlay facility, fully integrated with the system's linker, loader, and
  20682.    virtual memory manager. Much of OS/2 itself, including most API entry
  20683.    points and the code for API functions that do not need to run at ring 0,
  20684.    is distributed in the form of dynlink libraries (see Chapter 2). This
  20685.    rather elegant merger of OS/2's services and its implementation keeps the
  20686.    system's fixed memory requirements to a minimum.
  20687.  
  20688.    The routines in a dynlink library execute in user mode in the context of
  20689.    the client process. In other words, as far as the kernel's scheduler is
  20690.    concerned, a thread executing within a process is no different from a
  20691.    thread within a dynlink library bound to the process. No stack switching
  20692.    is involved; the library is viewed as an extension of the process.
  20693.    Consequently, the routines in a dynlink library have unrestricted access
  20694.    to the calling process's memory and its other resources, such as file,
  20695.    pipe, semaphore, and queue handles.
  20696.  
  20697.    From the point of view of an application, however, every dynlink library
  20698.    (regardless of its origin) is indistinguishable from a component of the
  20699.    operating system: Each DLL routine accepts its parameters on the stack, is
  20700.    entered by a far call to a named entry point, and returns a status code in
  20701.    AX. Details as to the way a DLL routine does its work are invisible to the
  20702.    application; within the DLL, the routine might continue to execute in user
  20703.    mode, invoke an IOPL segment, request action by the kernel on behalf of
  20704.    the application, or use IPC mechanisms to request services from a server
  20705.    process on the same or another machine.
  20706.  
  20707.    The system loader has yet another perspective. To the loader, every
  20708.    segmented executable file, whether it contains a DLL or an application, is
  20709.    simply another system resource called a module. The module name is
  20710.    contained in the file header and may or may not be the same as the name of
  20711.    the file. While a program is running or a dynlink library is in use, the
  20712.    segment and entry point tables from the EXE file header are kept resident
  20713.    so that the loader can easily locate discardable segments and LOADONCALL
  20714.    segments when they are needed.
  20715.  
  20716.    If an additional copy of the same program is started, or if another
  20717.    process references the same library, the loader already has the necessary
  20718.    information in hand so that the system overhead for creating the new
  20719.    process or dynamic link is relatively small. The loader maintains a use
  20720.    count for each module. When all the instances of a program have
  20721.    terminated, or when all the clients of a dynlink library have released it
  20722.    or terminated, the module use count becomes zero, and the loader discards
  20723.    the module and associated tables and reclaims the memory.
  20724.  
  20725.    This behavior of the loader has an interesting side effect. Any time that
  20726.    a program is running or that a dynlink library is in use, the file
  20727.    containing that module is held open by the loader with a sharing mode of
  20728.    deny-write; during this time, the file cannot be renamed, erased, or
  20729.    replaced. (This prohibition improves performance and reliability by
  20730.    eliminating multiple open/close operations and ensuring that a discardable
  20731.    segment cannot be modified between loads.) Because the operating system's
  20732.    base DLLs (such as DOSCALL1.DLL and VIOCALLS.DLL) are always in use by the
  20733.    Presentation Manager and CMD.EXE, you can't replace one of these DLLs on
  20734.    your bootable fixed disk unless you reboot the system from a
  20735.    self-contained floppy disk that does not use them.
  20736.  
  20737.    Dynlink libraries differ in important ways from the two other kinds of
  20738.    libraries found in OS/2: object libraries and import libraries. Dynlink
  20739.    libraries are structured as segmented executable (new EXE) files with a
  20740.    special flag in the file header that prevents your running them directly
  20741.    from the command line; the routines in a DLL are suitable for immediate
  20742.    execution and are bound to an application (dynamic linking) at load time
  20743.    or run time.
  20744.  
  20745.    In contrast, object libraries and import libraries are used at link time
  20746.    rather than at execution time and are stored in a special format
  20747.    understood only by the LINK, LIB, and IMPLIB utilities. The code in an
  20748.    object library requires further processing by the Linker before it can be
  20749.    executed, and it is permanently incorporated into a program or a DLL
  20750.    executable file (static linking). The records in import libraries do not
  20751.    usually contain executable code at all; they are merely stubs that
  20752.    represent individual DLL routines and signal the Linker to build the
  20753.    information necessary for dynamic linking into the EXE file header.
  20754.  
  20755.  
  20756.  Dynamic Linking at Load Time
  20757.  
  20758.    Dynamic linking at load time is easy to arrange. In fact, given that most
  20759.    OS/2 API entry points actually reside in dynlink libraries, you can
  20760.    appreciate that you have already been exposed to dynamic linking in every
  20761.    program in this book!
  20762.  
  20763.    In a MASM program, the routines that are to be dynamically linked are
  20764.    simply declared as external with the far attribute, for example:
  20765.  
  20766.    extrn DosWrite:far
  20767.  
  20768.    In a C program, the routines are declared as external with the far pascal
  20769.    attributes (parameters pushed from left to right, and called routine
  20770.    clears the stack), for example:
  20771.  
  20772.    extern unsigned far pascal DosWrite( ... );
  20773.  
  20774.    If you include the API header files supplied with OS/2 development tools,
  20775.    these external declarations are made for you already.
  20776.  
  20777.    When you link your program, you either specify the module and entry point
  20778.    names for the dynlinked routines with IMPORTS statements in the module
  20779.    definition (DEF) file, or you link the program with an import library that
  20780.    matches the dynlink library or libraries you are going to use at run time.
  20781.    (For example, OS2.LIB contains import records for the Dos, Kbd, Mou, and
  20782.    Vio API entry points.) The result of either method is the same: The Linker
  20783.    builds the names of the dynlink libraries and routines into tables in the
  20784.    EXE file header, along with pointers to each reference to the dynlink
  20785.    libraries in the executable code.
  20786.  
  20787.    Imported dynlink library routines can also be specified in a module
  20788.    definition file by an ordinal rather than by name. An ordinal is merely
  20789.    the index to the position of the routine in the library's entry point
  20790.    table. Using ordinals speeds up dynamic linking and reduces the size of
  20791.    your application's EXE file (because the imported name table in the header
  20792.    will be smaller). But linking with ordinals has its disadvantages. You
  20793.    must be sure that you do not change the ordinals from one version of the
  20794.    library to another, or you must synchronize the library and all the
  20795.    applications that use it. Either way, you forfeit flexibility by using
  20796.    ordinals.
  20797.  
  20798.    When your program is loaded for execution, the system loader inspects the
  20799.    file header's module reference table to determine which dynlink libraries
  20800.    the process uses. If these libraries are not already loaded, the loader
  20801.    invokes itself recursively to bring them into memory (along with any
  20802.    libraries they reference). The loader always assumes that a dynlink
  20803.    library has the extension DLL and that it can be found in one of the
  20804.    directories named in the LIBPATH directive in CONFIG.SYS. If any dynlink
  20805.    library cannot be located, or if memory is exhausted during the loading of
  20806.    libraries, the client program will not be loaded.
  20807.  
  20808.    Some dynlink libraries have an initialization routine which must be called
  20809.    when they are loaded for the first time (global initialization) or each
  20810.    time they are bound to a new process (instance initialization). The type
  20811.    of initialization required, if any, is specified in the library's file
  20812.    header, as is the entry point of the initialization routine. The
  20813.    initialization routine might return an error code if it can't locate
  20814.    resources that the dynlink library will need later (such as a font file, a
  20815.    device, or additional memory), in which case loading of the client program
  20816.    is aborted.
  20817.  
  20818.    When all necessary libraries are loaded and any necessary initialization
  20819.    of those libraries is complete, the loader inspects the segment tables for
  20820.    each module and creates an LDT selector for each program and dynlink
  20821.    library segment. All segments marked PRELOAD are brought into memory, and
  20822.    any necessary fixups are performed. (Segments marked LOADONCALL are not
  20823.    brought into memory and fixed up until they are referenced during program
  20824.    execution.)
  20825.  
  20826.  
  20827.  Dynamic Linking at Run Time
  20828.  
  20829.    Instead of letting the Linker and system loader do all the work of dynamic
  20830.    linking, you can elect to have your program create dynamic links to the
  20831.    libraries it needs (function by function) while it is executing. The API
  20832.    functions for runtime dynamic linking are summarized in Figure 19-1.
  20833.  
  20834.    Function                  Description
  20835.    ──────────────────────────────────────────────────────────────────────────
  20836.    DosFreeModule            Releases linkage between process and dynlink
  20837.                              library
  20838.    DosGetModHandle          Returns handle for dynlink library if it is
  20839.                              already loaded
  20840.    DosGetModName            Retrieves fully qualified pathname of dynlink
  20841.                              library
  20842.    DosGetProcAddr           Obtains address of entry point within dynlink
  20843.                              library
  20844.    DosLoadModule            Loads dynlink library if not already loaded and
  20845.                              returns handle for use with DosGetProcAddr
  20846.    ──────────────────────────────────────────────────────────────────────────
  20847.  
  20848.    Figure 19-1.  Summary of OS/2 API calls used by applications to link to
  20849.    DLLs at run time.
  20850.  
  20851.    To establish dynamic links at run time, the program must first make the
  20852.    dynlink library's segments and entry points known to the system loader by
  20853.    calling DosLoadModule. This function requires the addresses of an ASCIIZ
  20854.    module name, a buffer, and a variable to receive a handle. The system
  20855.    loader looks in the directories named in the LIBPATH directive for a
  20856.    file with the specified module name and the extension DLL. If the library
  20857.    is found, its PRELOAD segments are brought into memory, any additional
  20858.    libraries that it references are also loaded, and a module handle is
  20859.    returned. If the library (or a library that it uses) is not found,
  20860.    DosLoadModule returns an error code, and the name of the missing library
  20861.    is returned in the calling process's buffer.
  20862.  
  20863.    Assuming that DosLoadModule is successful and a handle is obtained, the
  20864.    program must then obtain the entry points for each library function that
  20865.    it will use. To do so, it calls DosGetProcAddr with a module handle and
  20866.    the address of an ASCIIZ entry point name or an ASCIIZ or binary ordinal.
  20867.    DosGetProcAddr returns a selector and offset for the entry point in a
  20868.    variable. The program can then invoke the library routine by pushing the
  20869.    appropriate parameters onto the stack and performing an indirect far call
  20870.    through the variable.
  20871.  
  20872.    After the program finishes using the dynlink library, it can release the
  20873.    library by calling DosFreeModule with the module handle. If no other
  20874.    process is using the library, it will be discarded and the memory it
  20875.    occupied will be reclaimed. After you call DosFreeModule, any entry points
  20876.    which were previously obtained for the library with DosGetProcAddr become
  20877.    invalid, and any attempt to use the library causes a GP fault. Figure
  20878.    19-2 contains a skeleton example of runtime dynamic linking in a MASM
  20879.    program.
  20880.  
  20881.    ──────────────────────────────────────────────────────────────────────────
  20882.    objbuf  db      64 dup (0)      ; receives failing module
  20883.                                    ; or entry point name
  20884.  
  20885.    objbuf_len equ  $-objbuf
  20886.  
  20887.    mname   db      'VIOCALLS',0    ; module name
  20888.  
  20889.    mhandle dw      0               ; receives module handle
  20890.  
  20891.    ename   db      'VIOGETANSI',0  ; entry point name
  20892.  
  20893.    eptr    dd      0               ; receives far pointer
  20894.                                    ; to entry point
  20895.  
  20896.            .
  20897.            .
  20898.            .
  20899.                                    ; attach to DLL...
  20900.            push    ds              ; receives failing dynlink
  20901.            push    offset DGROUP:objbuf
  20902.            push    objbuf_len      ; length of buffer
  20903.            push    ds              ; address of module name
  20904.            push    offset DGROUP:mname
  20905.            push    ds              ; receives module handle
  20906.            push    offset DGROUP:mhandle
  20907.            call    DosLoadModule   ; transfer to OS/2
  20908.            or      ax,ax           ; module found?
  20909.            jnz     error           ; jump if no such module
  20910.  
  20911.                                    ; get entry point...
  20912.            push    mhandle         ; module handle
  20913.            push    ds              ; address of entry name
  20914.            push    offset DGROUP:ename
  20915.            push    ds              ; receives entry pointer
  20916.            push    offset DGROUP:eptr
  20917.            call    DosGetProcAddr  ; transfer to OS/2
  20918.            or      ax,ax           ; entry point found?
  20919.            jnz     error           ; jump if no entry point
  20920.  
  20921.            .
  20922.            .                       ; other processing here...
  20923.            .
  20924.  
  20925.            call    eptr            ; indirect call to
  20926.                                    ; dynlink library routine
  20927.  
  20928.            .
  20929.            .                      ; other processing here...
  20930.            .
  20931.  
  20932.                                    ; module no longer needed,
  20933.                                    ; release it...
  20934.            push    mhandle         ; module handle
  20935.            call    DosFreeModule   ; transfer to OS/2
  20936.            or      ax,ax           ; should never fail
  20937.            jnz     error           ; but check anyway
  20938.            .
  20939.            .
  20940.            .
  20941.    ──────────────────────────────────────────────────────────────────────────
  20942.  
  20943.    Figure 19-2.  Sample code sequence for runtime dynamic linking.
  20944.  
  20945.    The two other API functions for dynamic linking at run time are rarely
  20946.    used. DosGetModName accepts a module handle (for a library or a process)
  20947.    and retrieves the fully qualified pathname for the module. DosGetModHandle
  20948.    accepts an ASCIIZ module name and returns a handle for the module if it is
  20949.    already loaded, or an error if it is not loaded; DosGetModHandle is
  20950.    strictly an existence test and does not increment the module's use count.
  20951.  
  20952.  
  20953.  Designing New Dynlink Libraries
  20954.  
  20955.    Designing and writing a new dynlink library is a straightforward process
  20956.    in either MASM or C. During the planning stage, you must address the
  20957.    following three design issues:
  20958.  
  20959.    ■  The nature of the DLL interface to applications
  20960.  
  20961.    ■  The type of DLL initialization (if any)
  20962.  
  20963.    ■  The number and behavior of DLL data segments
  20964.  
  20965.    You need to gauge carefully the range and complexity of the functions that
  20966.    the DLL will export to applications. If the functions are too few and too
  20967.    specific, the application programmer will be forced to write his or her
  20968.    own routines for special cases. On the other hand, if the routines are too
  20969.    numerous, the programmer will have difficulty remembering what is
  20970.    available. And if the routines are too primitive, the application
  20971.    programmer will not gain enough leverage by using the functions to make
  20972.    the effort worthwhile. The Vio subsystem that is supplied with OS/2 is an
  20973.    excellent example to imitate.
  20974.  
  20975.    When you write a MASM dynlink library for use with MASM applications, the
  20976.    parameter passing between the application and the DLL can be
  20977.    register-based, stack-based, or a hybrid of the two. When you write a DLL
  20978.    in C, or one that is to be used with C applications, you have less
  20979.    freedom; the structure of the C language more or less forces you to use
  20980.    the same stack-based API as the "standard" OS/2 DLLs, for which the only
  20981.    open question is what each "function" will return as its "value." In
  20982.    either case, however, I strongly recommend that you follow the kernel API
  20983.    conventions──pass all parameters to the DLL on the stack, and return a
  20984.    status code in AX and any other information to the caller's variables.
  20985.    Symmetry with the rest of the system will reward you with fewer exceptions
  20986.    to remember and hence fewer bugs to fix.
  20987.  
  20988.    An initialization routine in your DLL is optional. If you provide one,
  20989.    specify its entry point with an END statement in a MASM source file. A
  20990.    dynlink library written in C that requires initialization must, therefore,
  20991.    have at least one small module written in MASM, although the MASM routine
  20992.    which receives control at initialization time can call a C function to do
  20993.    the actual work.
  20994.  
  20995.    If the DLL has an initialization routine, you must determine whether it
  20996.    will be called only when the library is first loaded (global
  20997.    initialization) or each time the library is attached by a client process
  20998.    (instance initialization). Global initialization is especially appropriate
  20999.    when a DLL controls a device; if the device is not available, the
  21000.    initialization routine returns an error. Instance initialization is useful
  21001.    when the DLL allocates private data structures or system resources on a
  21002.    per-client basis. In such cases, the initialization routine should also
  21003.    register a clean-up routine for each client with DosExitList so that the
  21004.    indicated routine will be notified when the client terminates and can
  21005.    clean up any resources or data that have been left hanging.
  21006.  
  21007.    The question of the DLL's data segments goes hand in hand with global or
  21008.    instance initialization. Some DLLs need no data segments of their own──
  21009.    those that have no initialized data and no need to store any information
  21010.    across calls from the application, and that can use the stack for all
  21011.    their transient variables. Other DLLs, such as those controlling a single
  21012.    system resource such as a device, might need only a single data segment to
  21013.    store the state of the device and synchronize access to it by multiple
  21014.    processes.
  21015.  
  21016.    The more common case, however, is the DLL which needs multiple unshared
  21017.    segments to store information across function calls on a per-client basis.
  21018.    Each time a new process attaches to the library, OS/2 loads a fresh copy
  21019.    of the DLL's data segments to map into the process's memory space. The
  21020.    system uses identical selectors in each process's LDT so that the same
  21021.    relocations in the DLL's shared code segments will serve for all. The
  21022.    selector magic inherent in OS/2's support for dynlink instance data is one
  21023.    of the most subtle but exquisite features of OS/2's design. During
  21024.    instance initialization, a DLL can also dynamically allocate memory that
  21025.    it can use on behalf of the client process.
  21026.  
  21027.    Although the nature of the DLL's application interface is defined by its
  21028.    source code, the initialization type (global or instance) and behavior of
  21029.    each segment (preload or load-on-call, shared or unshared) is controlled
  21030.    by keywords in the module definition file. Figure 19-3 on the following
  21031.    page provides a summary of the definition file syntax for dynlink
  21032.    libraries (and for device drivers). Compare this summary with the similar
  21033.    figure in Chapter 3 on p. 33; some of the directives and defaults are
  21034.    different from those for applications. Appendix E provides a complete
  21035.    explanation of the syntax for module definition files.
  21036.  
  21037.                          ┌──────────────────────────────────────────────────┐
  21038.                          │ Ignored if no initialization routine is present. │
  21039.                          └──────┬───────────────────────────────────────────┘
  21040.                           ┌─────┴─────────────────────┐
  21041.    LIBRARY [ modulename ][ INITGLOBAL | INITINSTANCE ]
  21042.  
  21043.    DESCRIPTION `string'
  21044.  
  21045.    CODE [ PRELOAD | LOADONCALL ][ EXECUTEONLY | EXECUTEREAD ]
  21046.        [ IOPL | NOIOPL ][ CONFORMING | NONCONFORMING ]
  21047.  
  21048.    DATA [ PRELOAD | LOADONCALL ][ READONLY | READWRITE ]
  21049.        [ NONE | SINGLE | MULTIPLE ][ SHARED | NONSHARED ]
  21050.        [ IOPL | NOIOPL ]
  21051.  
  21052.    SEGMENTS
  21053.        name [ CLASS [`classname']][ PRELOAD | LOADONCALL ]
  21054.             [ READONLY | READWRITE ][ EXECUTEONLY | EXECUTEREAD ]
  21055.             [ IOPL | NOIOPL ][ CONFORMING | NONCONFORMING ]
  21056.             [ SHARED | NONSHARED ]
  21057.            └───────────┬──────────┘
  21058.             .          └─────────────────────────┐
  21059.             .             ┌──────────────────────┴───────────────────────┐
  21060.             .             │ Ignored for code segments and read-only data │
  21061.                           │ segments; these segments are always shared.  │
  21062.    EXPORTS                └──────────────────────────────────────────────┘
  21063.        exportname [ = internalname ][ @ordinal ][ RESIDENTNAME ][ stackparams
  21064.                                                                    └───────┬──
  21065.        .     ┌─────────────────────────────────────────────────────────────┴──
  21066.        .     │ Defines the number of stack cells passed to a procedure within
  21067.        .     │ IOPL segment; when the routine is called, the hardware switches
  21068.              │ stacks and copies the specified number of words to the new stac
  21069.    IMPORTS   └────────────────────────────────────────────────────────────────
  21070.        [ internalname = ] modulename.entryname | modulename.ordinal
  21071.        .
  21072.        .
  21073.        .
  21074.    STUB `filename.EXE'
  21075.  
  21076.    OLD `filename.DLL'
  21077.  
  21078.    EXETYPE OS2
  21079.  
  21080.    Figure 19-3.  Module definition file elements used for dynlink libraries
  21081.    and installable device drivers. Keywords must always be entered in
  21082.    uppercase letters. Defaults are in boldface and are not necessarily the
  21083.    same as those supplied for application programs.
  21084.  
  21085.  
  21086.  Writing DLLs in MASM
  21087.  
  21088.    Figure 19-4 contains a code skeleton for a DLL written in MASM. Each
  21089.    procedure in the DLL should set up a stack frame so that it can access its
  21090.    parameters, save any registers that it is going to change, and reset the
  21091.    DS register to make its data segment addressable (if it has one).
  21092.    Procedures that serve as entry points must be declared public and far
  21093.    because they are entered by an intersegment call; procedures that are used
  21094.    only as subroutines within the DLL itself can be declared either near or
  21095.    far. To exit, a procedure should restore the caller's registers (except
  21096.    for AX) and then use RET with an offset to clear the parameters (if any)
  21097.    from the caller's stack.
  21098.  
  21099.    ──────────────────────────────────────────────────────────────────────────
  21100.    DGROUP  group   _DATA
  21101.  
  21102.    _DATA   segment word public 'DATA'
  21103.            .
  21104.            .
  21105.            .
  21106.    _DATA   ends
  21107.  
  21108.    _TEXT   segment word public 'CODE'
  21109.  
  21110.            assume  cs:_TEXT,ds:DGROUP
  21111.  
  21112.            public  MYFUNC
  21113.  
  21114.    MYFUNC  proc    far             ; dynlink routine
  21115.  
  21116.            push    bp              ; set up stack frame
  21117.            mov     bp,sp
  21118.  
  21119.            push    ax              ; save affected registers
  21120.            push    bx
  21121.            push    cx
  21122.            push    dx
  21123.            push    si
  21124.            push    di
  21125.            push    ds
  21126.  
  21127.            mov     ax,DGROUP       ; make DLL's data
  21128.            mov     ds,ax           ; segment addressable
  21129.  
  21130.            .
  21131.            .
  21132.            .
  21133.            pop     ds              ; restore registers
  21134.            pop     di
  21135.            pop     si
  21136.            pop     dx
  21137.            pop     cx
  21138.            pop     bx
  21139.            pop     ax
  21140.            pop     bp
  21141.  
  21142.            ret     pars*2          ; clear stack, return
  21143.                                    ; to caller
  21144.    MYFUNC  endp
  21145.  
  21146.  
  21147.    INIT    proc    far             ; initialization routine
  21148.  
  21149.            .
  21150.            .
  21151.            .
  21152.            ret
  21153.  
  21154.    INIT    endp
  21155.  
  21156.    _TEXT   ends
  21157.  
  21158.            end     INIT            ; initialization entry point
  21159.    ──────────────────────────────────────────────────────────────────────────
  21160.  
  21161.    Figure 19-4.  Skeleton for a DLL written in MASM.
  21162.  
  21163.    If an initialization routine is present, it is identified by the END
  21164.    directive and is also coded as a far procedure. Before calling the DLL
  21165.    initialization entry point, OS/2 sets up the registers as shown in Figure
  21166.    19-5.
  21167.  
  21168.    All other register values are undefined. If the module saves a copy of its
  21169.    own handle, it can pass the handle to client applications later, and they
  21170.    can use the handle to retrieve the library's fully qualified pathname with
  21171.    DosGetModName.
  21172.  
  21173.    Register                  Contents
  21174.    ──────────────────────────────────────────────────────────────────────────
  21175.    CS:IP                     Entry point defined in END directive
  21176.    SS:SP                     Current user stack
  21177.    DS                        Selector for DLL's DGROUP, if any; otherwise,
  21178.                              selector for DGROUP of process attaching to DLL
  21179.    AX                        Module handle of the DLL
  21180.    ──────────────────────────────────────────────────────────────────────────
  21181.  
  21182.    Figure 19-5.  Register contents at entry to a DLL initialization routine.
  21183.  
  21184.    The initialization routine exits with a far return, passing a status code
  21185.    in AX. Initialization routines depart from the conventions observed
  21186.    elsewhere in OS/2: They return zero to indicate an error and a nonzero
  21187.    value to indicate success. If the status returned is zero, the program
  21188.    that referenced the library is not loaded, and the name of the failing
  21189.    library is passed to that program's parent.
  21190.  
  21191.    The module definition file for our skeleton DLL is shown in Figure 19-6.
  21192.    Real DLLs with many segments might have elaborate DEF files, but this one
  21193.    is quite simple. The key elements are the LIBRARY directive, which signals
  21194.    the Linker that entry point and stack declarations may be absent, and the
  21195.    EXPORTS statement, which designates the entry points for applications.
  21196.    Instance or global initialization and DGROUP for the DLL are controlled by
  21197.    optional parameters to the LIBRARY and DATA directives. Assign PRELOAD or
  21198.    LOADONCALL to each segment based on the likelihood that it will be used.
  21199.    In a simple DLL with only two segments (such as this skeleton example),
  21200.    both might as well be marked PRELOAD so that the time required to fetch
  21201.    them from disk will be hidden inside the overall load time of the
  21202.    application.
  21203.  
  21204.    ──────────────────────────────────────────────────────────────────────────
  21205.    LIBRARY MYDLL INITGLOBAL
  21206.    CODE PRELOAD
  21207.    DATA PRELOAD MULTIPLE
  21208.    EXPORTS
  21209.      MYFUNC
  21210.    ──────────────────────────────────────────────────────────────────────────
  21211.  
  21212.    Figure 19-6.  Module definition file for the skeleton MASM dynlink
  21213.    library.
  21214.  
  21215.  
  21216.  A Complete Sample DLL
  21217.  
  21218.    Figure 19-7 (on the following pages) and Figure 19-8 (on p. 476) contain
  21219.    the MASM source code and module definition file for a complete sample
  21220.    dynlink library called ASMHELP.DLL. The library exports two routines for
  21221.    use by MASM applications: ARGC, which counts the number of command line
  21222.    arguments, and ARGV, which returns the address and length of a specific
  21223.    command line argument.
  21224.  
  21225.    ──────────────────────────────────────────────────────────────────────────
  21226.            title   ASMHELP -- Sample MASM DLL
  21227.            page    55,132
  21228.            .286
  21229.  
  21230.    ;
  21231.    ; ASMHELP.ASM
  21232.    ;
  21233.    ; Source code for the MASM dynlink library ASMHELP.DLL.
  21234.    ;
  21235.    ; Assemble with  C> masm asmhelp.asm;
  21236.    ; Link with  C> link asmhelp,asmhelp.dll,,os2,asmhelp
  21237.    ;
  21238.    ; Exports two routines for use by MASM programs:
  21239.    ;
  21240.    ; ARGC  Returns count of command line arguments.
  21241.    ;       Treats blanks and tabs as whitespace.
  21242.    ;
  21243.    ;       Calling sequence:
  21244.    ;
  21245.    ;       push    seg argcnt      ; receives argument count
  21246.    ;       push    offset argcnt
  21247.    ;       call    ARGC
  21248.    ;
  21249.    ; ARGV  Returns address and length of specified
  21250.    ;       command line argument.  If called for
  21251.    ;       argument 0, returns address and length
  21252.    ;       of fully qualified pathname of program.
  21253.    ;
  21254.    ;       Calling sequence:
  21255.    ;
  21256.    ;       push    argno           ; argument number
  21257.    ;       push    seg argptr      ; receives argument address
  21258.    ;       push    offset argptr
  21259.    ;       push    seg arglen      ; receives argument length
  21260.    ;       push    offset arglen
  21261.    ;       call    ARGV
  21262.    ;
  21263.    ; Copyright (C) 1988 Ray Duncan
  21264.    ;
  21265.  
  21266.    tab     equ     09h             ; ASCII tab
  21267.    blank   equ     20h             ; ASCII space character
  21268.            extrn   DosGetEnv:far
  21269.  
  21270.    DGROUP  group   _DATA
  21271.  
  21272.  
  21273.    _DATA   segment word public 'DATA'
  21274.  
  21275.    envseg  dw      ?               ; environment selector
  21276.    cmdoffs dw      ?               ; command tail offset
  21277.  
  21278.    _DATA   ends
  21279.  
  21280.  
  21281.    _TEXT   segment word public 'CODE'
  21282.  
  21283.            assume  cs:_TEXT,ds:DGROUP
  21284.  
  21285.                                    ; parameter for argc
  21286.    argcnt  equ     [bp+6]          ; receives argument count
  21287.  
  21288.            public  argc
  21289.    argc    proc    far             ; count command line arguments
  21290.  
  21291.            push    bp              ; make arguments addressable
  21292.            mov     bp,sp
  21293.  
  21294.            push    ds              ; save registers
  21295.            push    es
  21296.            push    bx
  21297.            push    cx
  21298.  
  21299.            mov     ax,seg DGROUP   ; point to instance data
  21300.            mov     ds,ax
  21301.  
  21302.            mov     es,envseg       ; set ES:BX = command line
  21303.            mov     bx,cmdoffs
  21304.  
  21305.            mov     ax,1            ; initialize argument count
  21306.  
  21307.    argc0:  inc     bx              ; ignore useless first field
  21308.            cmp     byte ptr es:[bx],0
  21309.            jne     argc0
  21310.  
  21311.    argc1:  mov     cx,-1           ; set flag = outside argument
  21312.    argc2:  inc     bx              ; point to next character
  21313.  
  21314.            cmp     byte ptr es:[bx],0
  21315.            je      argc3           ; exit if null byte
  21316.  
  21317.            cmp     byte ptr es:[bx],blank
  21318.            je      argc1           ; outside argument if ASCII blank
  21319.  
  21320.            cmp     byte ptr es:[bx],tab
  21321.            je      argc1           ; outside argument if ASCII tab
  21322.  
  21323.                                    ; otherwise not blank or tab,
  21324.            jcxz    argc2           ; jump if already inside argument
  21325.  
  21326.            inc     ax              ; else found argument, count it
  21327.            not     cx              ; set flag = inside argument
  21328.            jmp     argc2           ; and look at next character
  21329.  
  21330.    argc3:                          ; store result into
  21331.                                    ; caller's variable...
  21332.  
  21333.            les     bx,argcnt       ; get address of variable
  21334.            mov     es:[bx],ax      ; store argument count
  21335.  
  21336.            pop     cx              ; restore registers
  21337.            pop     bx
  21338.            pop     es
  21339.            pop     ds
  21340.            pop     bp
  21341.  
  21342.            xor     ax,ax           ; signal success
  21343.  
  21344.            ret     4               ; return to caller
  21345.                                    ; and discard parameters
  21346.    argc    endp
  21347.  
  21348.  
  21349.  
  21350.                                    ; parameters for argv...
  21351.    argno   equ     [bp+14]         ; argument number
  21352.    argptr  equ     [bp+10]         ; receives argument pointer
  21353.    arglen  equ     [bp+6]          ; receives argument length
  21354.            public  argv
  21355.    argv    proc    far             ; get address and length of
  21356.                                    ; command tail argument
  21357.  
  21358.            push    bp              ; make arguments addressable
  21359.            mov     bp,sp
  21360.  
  21361.            push    ds              ; save registers
  21362.            push    es
  21363.            push    bx
  21364.            push    cx
  21365.            push    di
  21366.  
  21367.            mov     ax,seg DGROUP   ; point to instance data
  21368.            mov     ds,ax
  21369.  
  21370.            mov     es,envseg       ; set ES:BX = command line
  21371.            mov     bx,cmdoffs
  21372.  
  21373.            mov     ax,argno        ; get argument number
  21374.            or      ax,ax           ; requesting argument 0?
  21375.            jz      argv8           ; yes, get program name
  21376.  
  21377.    argv1:  inc     bx              ; scan off first field
  21378.            cmp     byte ptr es:[bx],0
  21379.            jne     argv1
  21380.  
  21381.            xor     ah,ah           ; initialize argument counter
  21382.  
  21383.    argv2:  mov     cx,-1           ; set flag = outside argument
  21384.  
  21385.    argv3:  inc     bx              ; point to next character
  21386.  
  21387.            cmp     byte ptr es:[bx],0
  21388.            je      argv7           ; exit if null byte
  21389.  
  21390.            cmp     byte ptr es:[bx],blank
  21391.            je      argv2           ; outside argument if ASCII blank
  21392.  
  21393.            cmp     byte ptr es:[bx],tab
  21394.            je      argv2           ; outside argument if ASCII tab
  21395.  
  21396.                                    ; if not blank or tab...
  21397.            jcxz    argv3           ; jump if inside argument
  21398.            inc     ah              ; else count arguments found
  21399.            cmp     ah,al           ; is this the one?
  21400.            je      argv4           ; yes, go find its length
  21401.  
  21402.            not     cx              ; no, set flag = inside argument
  21403.            jmp     argv3           ; and look at next character
  21404.  
  21405.    argv4:                          ; found desired argument, now
  21406.                                    ; determine its length...
  21407.            mov     ax,bx           ; save parameter starting address
  21408.  
  21409.    argv5:  inc     bx              ; point to next character
  21410.  
  21411.            cmp     byte ptr es:[bx],0
  21412.            je      argv6           ; found end if null byte
  21413.  
  21414.            cmp     byte ptr es:[bx],blank
  21415.            je      argv6           ; found end if ASCII blank
  21416.  
  21417.            cmp     byte ptr es:[bx],tab
  21418.            jne     argv5           ; found end if ASCII tab
  21419.  
  21420.    argv6:  xchg    bx,ax           ; set ES:BX = argument address
  21421.            sub     ax,bx           ; and AX = argument length
  21422.            jmp     argv10          ; return to caller
  21423.  
  21424.    argv7:  mov     ax,1            ; set AX != 0 indicating
  21425.                                    ; error, argument not found
  21426.            jmp     argv11          ; return to caller
  21427.  
  21428.    argv8:                          ; special handling for argv = 0
  21429.            xor     di,di           ; find the program name by
  21430.            xor     al,al           ; first skipping over all the
  21431.            mov     cx,-1           ; environment variables...
  21432.            cld
  21433.  
  21434.    argv9:  repne scasb             ; scan for double null (can't use SCASW
  21435.            scasb                   ; because it might be odd address)
  21436.            jne     argv9           ; loop if it was a single null
  21437.  
  21438.            mov     bx,di           ; save program name address
  21439.            mov     cx,-1           ; now find its length...
  21440.            repne scasb             ; scan for another null byte
  21441.            not     cx              ; convert CX to length
  21442.            dec     cx
  21443.            mov     ax,cx           ; return length in AX
  21444.  
  21445.    argv10:                         ; at this point AX = length,
  21446.                                    ; ES:BX points to argument
  21447.  
  21448.            lds     di,argptr       ; address of first variable
  21449.            mov     ds:[di],bx      ; store argument pointer
  21450.            mov     ds:[di+2],es
  21451.  
  21452.            lds     di,arglen       ; address of second variable
  21453.            mov     ds:[di],ax      ; store argument length
  21454.  
  21455.            xor     ax,ax           ; AX = 0 to signal success
  21456.  
  21457.    argv11:                         ; common exit point
  21458.  
  21459.            pop     di              ; restore registers
  21460.            pop     cx
  21461.            pop     bx
  21462.            pop     es
  21463.            pop     ds
  21464.            pop     bp
  21465.  
  21466.            ret     10              ; return to caller
  21467.                                    ; and discard parameters
  21468.    argv    endp
  21469.  
  21470.  
  21471.    init    proc    far             ; DLL instance initialization
  21472.  
  21473.                                    ; get environment selector
  21474.                                    ; and offset of command tail
  21475.                                    ; for this process...
  21476.  
  21477.            push    seg DGROUP      ; receives environment selector
  21478.            push    offset DGROUP:envseg
  21479.            push    seg DGROUP      ; receives command tail offset
  21480.            push    offset DGROUP:cmdoffs
  21481.            call    DosGetEnv       ; transfer to OS/2
  21482.            or      ax,ax           ; call successful?
  21483.            jnz     init1           ; no, initialization error
  21484.  
  21485.            mov     ax,1            ; initialization OK,
  21486.            ret                     ; return AX = 1 for success
  21487.    init1:  xor     ax,ax           ; initialization failed,
  21488.            ret                     ; return AX = 0 for error
  21489.  
  21490.    init    endp
  21491.  
  21492.    _TEXT   ends
  21493.  
  21494.            end     init            ; initialization entry point
  21495.    ──────────────────────────────────────────────────────────────────────────
  21496.  
  21497.    Figure 19-7.  ASMHELP.ASM, the MASM source code for the dynlink library
  21498.    ASMHELP.DLL.
  21499.  
  21500.    ──────────────────────────────────────────────────────────────────────────
  21501.    LIBRARY ASMHELP INITINSTANCE
  21502.    PROTMODE
  21503.    CODE LOADONCALL
  21504.    DATA LOADONCALL NONSHARED
  21505.    EXPORTS
  21506.         ARGC
  21507.         ARGV
  21508.    ──────────────────────────────────────────────────────────────────────────
  21509.  
  21510.    Figure 19-8.  ASMHELP.DEF, the module definition file for the dynlink
  21511.    library ASMHELP.DLL.
  21512.  
  21513.    The routines in ASMHELP.DLL are exactly analogous to the argc and argv
  21514.    available to C programs. Both are written according to the OS/2 API
  21515.    conventions. Parameters and the addresses of variables to receive results
  21516.    are passed on the stack. A status code is returned in AX (zero for no
  21517.    error), and any other information is placed in variables specified by the
  21518.    caller.
  21519.  
  21520.    The routine ARGC always returns a value of 1 or greater──the name of the
  21521.    program itself is counted as a command line argument. The parameter for
  21522.    ARGV is zero-based; when it is zero, the routine extracts from the
  21523.    environment the fully qualified pathname from which the current process
  21524.    was loaded; for other values, it fetches the corresponding token from the
  21525.    command tail.
  21526.  
  21527.    To create ASMHELP.DLL, type the following sequence of commands:
  21528.  
  21529.    [C:\] MASM ASMHELP.ASM;  <Enter>
  21530.    [C:\] LINK ASMHELP,ASMHELP.DLL,,OS2,ASMHELP  <Enter>
  21531.  
  21532.    Then copy the file to one of the directories named in the LIBPATH
  21533.    directive in CONFIG.SYS so that the system loader can find it.
  21534.  
  21535.    Figure 19-9 and Figure 19-10 (on p. 480) contain the source code and
  21536.    module definition file for SHOWARGS.EXE, a simple application that calls
  21537.    both routines in ASMHELP.DLL. To create SHOWARGS.EXE, type the following
  21538.    sequence of commands:
  21539.  
  21540.    [C:\] MASM SHOWARGS.ASM;  <Enter>
  21541.    [C:\] LINK SHOWARGS,,,OS2,SHOWARGS  <Enter>
  21542.  
  21543.    The following is a sample SHOWARGS session:
  21544.  
  21545.    [C:\] showargs foo bar  <Enter>
  21546.  
  21547.    The command line contains 03 arguments
  21548.    Argument 00 is:  C:\SOURCE\OS2\DLL\SHOWARGS.EXE
  21549.    Argument 01 is:  foo
  21550.    Argument 02 is:  bar
  21551.    [C:\]
  21552.  
  21553.    ──────────────────────────────────────────────────────────────────────────
  21554.            title     SHOWARGS -- ASMHELP.DLL Demo
  21555.            page      55,132
  21556.            .286
  21557.  
  21558.    ;
  21559.    ; SHOWARGS.ASM
  21560.    ;
  21561.    ; Demonstrates parsing of command line by calls to ASMHELP.DLL.
  21562.    ;
  21563.    ; Assemble with:  C> masm showargs.asm;
  21564.    ; Link with:  C> link showargs,,,os2,showargs
  21565.    ;
  21566.    ; Usage is:  C> showargs [argument(s)]
  21567.    ;
  21568.    ; Copyright (C) 1988 Ray Duncan
  21569.    ;
  21570.  
  21571.    stdin   equ     0               ; standard input handle
  21572.    stdout  equ     1               ; standard output handle
  21573.    stderr  equ     2               ; standard error handle
  21574.  
  21575.    cr      equ     0dh             ; ASCII carriage return
  21576.    lf      equ     0ah             ; ASCII linefeed
  21577.    blank   equ     020h            ; ASCII blank
  21578.    tab     equ     09h             ; ASCII tab
  21579.                                    ; references to ASMHELP.DLL
  21580.            extrn   ARGC:far        ; returns argument count
  21581.            extrn   ARGV:far        ; returns pointer to argument
  21582.  
  21583.            extrn   DosWrite:far    ; references to OS/2
  21584.            extrn   DosExit:far
  21585.            extrn   DosGetEnv:far
  21586.  
  21587.    DGROUP  group   _DATA
  21588.  
  21589.    _DATA   segment word public 'DATA'
  21590.  
  21591.    argno   dw      0               ; receives argument count
  21592.    argptr  dw      0,0             ; receives argument pointer
  21593.    arglen  dw      0               ; receives argument length
  21594.  
  21595.    curarg  dw      0               ; current command line argument
  21596.  
  21597.    wlen    dw      0               ; bytes actually written
  21598.  
  21599.    msg1    db      cr,lf
  21600.            db      'The command line contains '
  21601.    msg1a   db      'xx arguments'
  21602.    msg1_len equ $-msg1
  21603.  
  21604.    msg2    db      cr,lf
  21605.            db      'Argument '
  21606.    msg2a   db      'xx is:  '
  21607.    msg2_len equ $-msg2
  21608.  
  21609.    _DATA   ends
  21610.  
  21611.  
  21612.    _TEXT   segment word public 'CODE'
  21613.  
  21614.            assume  cs:_TEXT,ds:DGROUP
  21615.  
  21616.    main    proc    far             ; entry point from OS/2
  21617.  
  21618.                                    ; get number of command
  21619.                                    ; line arguments...
  21620.  
  21621.            push    ds              ; receives argument count
  21622.            push    offset DGROUP:argno
  21623.            call    argc            ; call ASMHELP.DLL
  21624.            mov     ax,argno        ; convert count to ASCII
  21625.            mov     bx,offset msg1a ; for output
  21626.            call    b2dec
  21627.  
  21628.                                    ; now display number
  21629.                                    ; of arguments...
  21630.  
  21631.            push    stdout          ; standard output handle
  21632.            push    ds              ; address of message
  21633.            push    offset DGROUP:msg1
  21634.            push    msg1_len        ; length of message
  21635.            push    ds              ; receives bytes written
  21636.            push    offset DGROUP:wlen
  21637.            call    DosWrite        ; transfer to OS/2
  21638.  
  21639.    main1:                          ; display next argument...
  21640.  
  21641.            mov     ax,curarg       ; are we all done?
  21642.            cmp     ax,argno
  21643.            je      main2           ; yes, exit
  21644.  
  21645.            mov     bx,offset msg2a ; no, convert argument number
  21646.            call    b2dec
  21647.  
  21648.                                    ; display argument number...
  21649.            push    stdout          ; standard output handle
  21650.            push    ds              ; address of message
  21651.            push    offset DGROUP:msg2
  21652.            push    msg2_len        ; length of message
  21653.            push    ds              ; receives bytes written
  21654.            push    offset DGROUP:wlen
  21655.            call    DosWrite        ; transfer to OS/2
  21656.  
  21657.                                    ; get argument pointer...
  21658.            push    curarg          ; argument number
  21659.            push    ds              ; receives pointer
  21660.            push    offset DGROUP:argptr
  21661.            push    ds              ; receives length
  21662.            push    offset DGROUP:arglen
  21663.            call    argv            ; call ASMHELP.DLL
  21664.  
  21665.                                    ; display the argument...
  21666.            push    stdout          ; standard output handle
  21667.            push    argptr+2        ; pointer to argument
  21668.            push    argptr
  21669.            push    arglen          ; length of argument
  21670.            push    ds              ; receives bytes written
  21671.            push    offset DGROUP:wlen
  21672.            call    DosWrite        ; transfer to OS/2
  21673.  
  21674.            inc     word ptr curarg ; go to next argument
  21675.            jmp     main1
  21676.  
  21677.    main2:                          ; common exit point
  21678.            push    1               ; terminate all threads
  21679.            push    0               ; return code = zero
  21680.            call    DosExit         ; transfer to OS/2
  21681.  
  21682.    main    endp
  21683.  
  21684.  
  21685.    b2dec   proc    near            ; convert binary value 0-99
  21686.                                    ; to two decimal ASCII
  21687.                                    ; call with
  21688.                                    ; AL = binary data
  21689.                                    ; BX = address for 2 characters
  21690.  
  21691.            aam                     ; divide AL by 10, leaving
  21692.                                    ; AH = quotient, AL = remainder
  21693.            add     ax,'00'         ; convert to ASCII
  21694.            mov     [bx],ah         ; store tens digit
  21695.            mov     [bx+1],al       ; store ones digit
  21696.            ret                     ; return to caller
  21697.  
  21698.    b2dec   endp
  21699.  
  21700.    _TEXT   ends
  21701.  
  21702.            end     main            ; defines entry point
  21703.    ──────────────────────────────────────────────────────────────────────────
  21704.  
  21705.    Figure 19-9.  SHOWARGS.ASM, source code for sample program SHOWARGS.EXE,
  21706.    which uses the dynlink library ASMHELP.DLL.
  21707.  
  21708.    ──────────────────────────────────────────────────────────────────────────
  21709.    NAME SHOWARGS
  21710.    PROTMODE
  21711.    STACKSIZE 4096
  21712.    IMPORTS
  21713.       ASMHELP.ARGC
  21714.       ASMHELP.ARGV
  21715.    ──────────────────────────────────────────────────────────────────────────
  21716.  
  21717.    Figure 19-10.  SHOWARGS.DEF, module definition file for SHOWARGS.EXE.
  21718.  
  21719.  
  21720.  Using Import Libraries
  21721.  
  21722.    If your dynlink library contains many entry points, you can make life much
  21723.    easier for the programmers who use it by creating a matching import
  21724.    library. When an application that calls the dynlink library is being
  21725.    built, the Linker can use the import library to satisfy internal
  21726.    references to the dynlink library routines, thereby eliminating the need
  21727.    for IMPORTS statements in the application's module definition file.
  21728.  
  21729.    Import libraries are built by the IMPLIB.EXE utility, which is supplied in
  21730.    the OS/2 Programmer's Toolkit. The parameters required by IMPLIB are the
  21731.    name of the import library to be built and the name of one or more module
  21732.    definition files containing EXPORTS statements for dynlink library entry
  21733.    points. The command line for IMPLIB takes the following form:
  21734.  
  21735.      IMPLIB libfile.LIB deffile.DEF [deffile.DEF ...]
  21736.  
  21737.    where the extensions LIB and DEF must be supplied as shown.
  21738.  
  21739.    For example, to create an import library for ASMHELP.DLL, the command line
  21740.    would be the following:
  21741.  
  21742.    [C:\] IMPLIB ASMHELP.LIB ASMHELP.DEF  <Enter>
  21743.    Once ASMHELP.LIB is available, you can remove the IMPORT statements from
  21744.    SHOWARGS.DEF and link the utility using the import library instead:
  21745.  
  21746.    [C:\] LINK SHOWARGS,,,OS2+ASMHELP,SHOWARGS  <Enter>
  21747.  
  21748.  
  21749.  Writing DLLs in C
  21750.  
  21751.    If you prefer to write your DLL in C, you need to use a few special
  21752.    tricks. First, each C function that will serve as an entry point from an
  21753.    application must be declared far pascal. Other functions, which are used
  21754.    only within the library, can use whichever style of parameter passing you
  21755.    prefer, although the pascal convention generates smaller, faster code. Pay
  21756.    close attention to every pointer because a dynlink routine is in some ways
  21757.    an extreme example of mixed-model C programming. Finally, include the
  21758.    following statement to prevent the C startup code from being dragged into
  21759.    the library by the Linker:
  21760.  
  21761.    int _acrtused = 0;
  21762.  
  21763.    When you compile your dynlink library, use the following switches:
  21764.  
  21765.    Switch                    Meaning
  21766.    ──────────────────────────────────────────────────────────────────────────
  21767.    /c                        Compile only (to allow you to link separately)
  21768.    /Asnu                     Small model; assume DS not equal to SS; save DS
  21769.                              and reset it to point to the library's data
  21770.                              segment at entry to each function
  21771.    /Gs                       Disable stack checking
  21772.    ──────────────────────────────────────────────────────────────────────────
  21773.  
  21774.    In some cases, you may also need to use the /ND switch to name the data
  21775.    segment that is made addressable when a DLL function is entered.
  21776.  
  21777.    The initialization routine, if any, must be written in MASM and assembled
  21778.    separately using the /Mx switch so that case is preserved in public and
  21779.    external names. The C-language body of the DLL must then be linked
  21780.    together with the MASM startup routine, OS2.LIB, and an appropriate module
  21781.    definition file.
  21782.  
  21783.    Figure 19-11 on the following page contains the source code (CDLL.C) for
  21784.    a simple dynlink library, one that merely displays a message when its
  21785.    initialization routine and exported function are called. Figure 19-12 on
  21786.    p. 484 contains the MASM source code (CINIT.ASM) for the initialization
  21787.    entry point, and Figure 19-13 on p. 484 is the module definition file
  21788.    (CDLL.DEF).
  21789.  
  21790.    ──────────────────────────────────────────────────────────────────────────
  21791.    /*
  21792.            CDLL.C
  21793.  
  21794.            C source code for the dynlink library CDLL.LIB.
  21795.            Requires the module CINIT.ASM.
  21796.  
  21797.            Compile with:  C> cl /c /Asnu /Gs cdll.c
  21798.            Assemble CINIT.ASM with:  C> masm /Mx cinit.asm;
  21799.            Link with:  C> link /NOI /NOD cdll+cinit,cdll.dll,,os2,cdll
  21800.            Create CDLL.LIB with:  C> implib cdll.lib cdll.def
  21801.  
  21802.            Copyright (C) 1988 Ray Duncan
  21803.    /*
  21804.  
  21805.    int _acrtused = 0;          /* don't link startup */
  21806.    #define STDOUT 1            /* standard output handle */
  21807.  
  21808.    #define API unsigned extern far pascal
  21809.  
  21810.    API DosWrite(unsigned, void far *, unsigned, unsigned far *);
  21811.  
  21812.    static char funcmsg[] = "\nCDLL.MYFUNC is executing\n";
  21813.    static char initmsg[] = "\nCDLL.C_INIT is executing\n";
  21814.  
  21815.  
  21816.    /*
  21817.        MYFUNC is exported for use by application programs;
  21818.        it displays a message and returns the sum of 2 numbers.
  21819.    */
  21820.  
  21821.    int far pascal MYFUNC(int a, int b)
  21822.    {
  21823.        unsigned wlen;          /* receives length written */
  21824.  
  21825.                                /* display message that
  21826.                                   MYFUNC is executing */
  21827.        DosWrite(STDOUT,funcmsg,sizeof(funcmsg)-1,&wlen);
  21828.  
  21829.        return(a+b);            /* return function result */
  21830.    }
  21831.  
  21832.  
  21833.    /*
  21834.        C_INIT is called from the entry point in CINIT.ASM when
  21835.        a client process dynamically links to the library.
  21836.    */
  21837.  
  21838.    int far pascal C_INIT(void)
  21839.    {
  21840.        unsigned wlen;          /* receives length written */
  21841.  
  21842.                                /* display message that
  21843.                                   C_INIT is executing */
  21844.        DosWrite(STDOUT,initmsg,sizeof(initmsg)-1,&wlen);
  21845.  
  21846.        return(1);              /* return success code */
  21847.    }
  21848.    ──────────────────────────────────────────────────────────────────────────
  21849.  
  21850.    Figure 19-11.  CDLL.C, source code for a simple dynlink library written in
  21851.    C. The function MYFUNC is exported for use by application programs; it
  21852.    displays a message and returns the sum of two numbers. The initialization
  21853.    function C_INIT is called when the DLL is first loaded from the entry
  21854.    point in CINIT.ASM (see Figure 19-12); in this example, it merely
  21855.    displays a message and returns a success code.
  21856.  
  21857.    ──────────────────────────────────────────────────────────────────────────
  21858.            extrn   C_INIT:far
  21859.  
  21860.    _TEXT   segment word public 'CODE'
  21861.  
  21862.            assume  cs:_TEXT
  21863.  
  21864.    INIT    proc    far             ; initialization routine
  21865.  
  21866.            call    C_INIT          ; call C routine to
  21867.                                    ; do the actual work
  21868.  
  21869.            ret                     ; back to caller with
  21870.                                    ; AX = status code
  21871.                                    ; ("value" from C_INIT)
  21872.    INIT    endp
  21873.  
  21874.    _TEXT   ends
  21875.  
  21876.            end     INIT
  21877.    ──────────────────────────────────────────────────────────────────────────
  21878.  
  21879.    Figure 19-12.  CINIT.ASM, the MASM module containing the initialization
  21880.    entry point for CDLL.DLL (see Figure 19-11). The MASM routine INIT simply
  21881.    calls the C function C_INIT to do the actual work of initialization.
  21882.  
  21883.    ──────────────────────────────────────────────────────────────────────────
  21884.    LIBRARY CDLL INITINSTANCE
  21885.    PROTMODE
  21886.    CODE LOADONCALL
  21887.    DATA LOADONCALL NONSHARED
  21888.    EXPORTS
  21889.         MYFUNC
  21890.    ──────────────────────────────────────────────────────────────────────────
  21891.  
  21892.    Figure 19-13.  CDLL.DEF, the module definition file for CDLL.DLL (see also
  21893.    Figures 19-11 and 19-12).
  21894.  
  21895.    To build the dynlink library CDLL.DLL, you would enter the following
  21896.    commands:
  21897.  
  21898.    [C:\] CL /c /Asnu /Gs CDLL.C  <Enter>
  21899.    [C:\] MASM /Mx CINIT.ASM;  <Enter>
  21900.    [C:\] LINK /NOI /NOD CDLL+CINIT,CDLL.DLL,,OS2,CDLL  <Enter>
  21901.  
  21902.    You can then create an import library for CDLL.DLL with the following
  21903.    command:
  21904.  
  21905.    [C:\] IMPLIB CDLL.LIB CDLL.DEF  <Enter>
  21906.  
  21907.    The entire process of building a dynlink library and an import library
  21908.    from MASM or C source code (or both) is diagrammed in Figure 19-14.
  21909.  
  21910.    If you use C runtime library functions in your DLL, be sure to use the
  21911.    special reentrant libraries so that your DLL can be used safely by more
  21912.    than one thread or process at a time.
  21913.  
  21914.    ┌───────────────┐     ┌───────────────┐
  21915.    │     MASM      │     │   C source    │
  21916.    │  source code  │     │   code file   │
  21917.    │  file (ASM)   │     │      (C)      │
  21918.    └───┬───────────┘     └───┬───────────┘
  21919.        │ MASM  ┌─────────────┘ Compiler
  21920.    ┌─────────────┐
  21921.    │  Relocatable  │
  21922.    │ object module ├─────┐
  21923.    │  file (OBJ)   │     │
  21924.    └───┬───────────┘     │
  21925.        │ LIB             │
  21926.    ┌──────────────┐     │
  21927.    │ Object module │     │
  21928.    │ library file  ├────│
  21929.    │     (LIB)     │     │
  21930.    └───────────────┘     │
  21931.    ┌───────────────┐     │           ┌───────────────┐
  21932.    │    Import     │