Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

29KB

Developing Your Own 32-Bit Operating System — Chapter-09: Application Programming

  • Source: /library/Developing Your Own 32-Bit Operating System/source-file.pdf
  • PDF pages: 149–162
  • Pages without text: 149, 150, 151
  • Note: Includes the Part III divider plate and its blank verso before the chapter.

MMURTL-Specific Details

Application Programming

Part III ■ MMURTL-Specific Details This section introduces the MMURTL operating-system interface and provides guidance on programming techniques specific to the MMURTL environment. It also provides informa- tion on basic video and keyboard usage as well as examples of multithreading with MMURTL and information on memory-management techniques and requirements. This chapter should be used with the reference manual for the language you are using. Before writing your first MMURTL program, you should be familiar with the material cov- ered in Chapter 4, “Interprocess Communications,” and Chapter 5, “Memory Management.” These chapters provide discussions of the concepts behind message-based operating systems and paged memory management. These are important concepts you should understand before you attempt to create or port your first application. Some of the information provided in this section may not actually be needed in order to code a program for MMURTL. I think you will find it useful, though, as you discover the methods employed by this operating system. It may even be entertaining. Terminology Throughout this section, I use several words that may seem language-specific or that mean different things to different programmers. The following definitions are provided to prevent confusion: Procedure A function in C or Pascal, or a procedure in Pascal, or a procedure in Assembler. All MMURTL operating system calls return values (called error codes). Function Same as procedure. Call Used interchangeably with junction and procedure. Byte An eight-bit value (signed or unsigned). Word A 16-bit value (signed or unsigned). Dword A 32-bit value (signed or unsigned). Parameter A value passed to a function or procedure, usually passed on the stack. Argument Same as parameter. Understanding 32-Bit Software If you have been programming with a 1 6-bit operating system (for example, MS-DOS), you will have to get used to the fact that all parameters to calls, and most structures (records) have many 32-bit components. If you are used to the 16-bit names for assembler variables, you should be comfortable with MMURTL because I have maintained the Intel and Microsoft conven- tions of byte, word, and dword Most MMURTL parameters to operating system calls are dwords

Access as byte 01 word 01 02 dword 01 02 Chapter 9 ■ Application Programming (32-bit values). Ifl were a perfectionist, I would have called a 32-bit value a word, but alas, old habits are hard to break and one of my key design goals was simplicity for the programmer on Intel-based ISA systems. One of the things that make processors different from each other is how data is stored and accessed in memory. The Intel processors have often been called “BackWords” because they store the least significant data bytes in lower memory addresses (hence, the byte values in a word appear reversed when viewed with a program that displays hexidecimal bytes). A byte at address 0 is accessed at the same memory location as a word at address 0, and a dword is also accessed at the same address. Look at the Table 9. 1 to visualize what you find in memory: Table 9.1. Memory alignment. Address 0 12 3 Hex value Olh 0201h 03 04 0403020 lh This alignment serves a very useful purpose. Conversions between types is automatic when read as a different type at the same address. This also plays a useful role in languages such as C, Pascal, and assembly language, although it's usually transparent to the programmer using a high- level language. When you are running the Intel processors in 32-bit protected mode, this also makes a great deal of difference when values are passed on the stack. You can push a dword on the stack and read it as a byte at the same location on the stack from a procedure. MMURTL always uses a dword stack. (Values are always pushed and popped as dwords — 32-bit values.) This is a hard- ware requirement of the 386/486 processors when 32-bit segments are used. Operating-System Calling Conventions All operating system calls are listed alphabetically in Chapter 15, “API Specification.” Most operating system calls in MMURTL return a dword (32-bit) error code, which is often abbreviated as ERC, ere, or dError. The descriptions in the API specification (Chapter 1 5) show it as dError to indicate it is a 32-bit unsigned value. These codes convey information to the caller about the status of the call. It may indicate an error, or it may simply convey information that may or may not be an error (depending on what you expect). A list of all documented status or error codes can be found in header files included with the source code. H

H Part III M MMURTL-Specific Details All public operating-system calls are accessible through call gates and can be reached from “out- side” programs. They are defined as far (this may be language-dependent) and require a 48-bit call address. A far call address to the operating system consists of a selector and an offset. The selector is the call gate in the Global Descriptor Table (GDT), while the offset- is ignored by the 386/486 processor. The GDT entry for the call gate defines the actual address that is called and the number of dwords of stack parameters to be passed to the call. You can even hard code the far address of the operating-system call because the system calls are at call gates. It doesn't matter where the operating system code is in memory. The call gate will never change unless it be- comes obsolete (in which case it will left in during several major revisions of the operating sys- tem for compatibility purposes). This means that an assembly-language programmer can make the far call to what appears to be a hard address (but is really a call gate). The addresses of the calls are documented with the source code (header or include files). Stack Usage Each of the MMURTL operating-system calls is defined as a function with parameters. Most return a value that is an error or status code. Some do not. All but a few of the operating-system calls use the stack to pass parameters to the operating- system functions. All parameters are pushed from left to right and are expanded to 32-bit values. This will be done by the compiler, and in some cases, even the assembler or processor itself. This is because the processor enforces a four-byte aligned stack. The called operating-system function always cleans the stack. In some C compilers, this is re- ferred to as the Pascal Calling Convention. The CM32 compiler uses this convention. It is also very easy to use from assembly language. The following is an example of a call to the operating system in assembler: PUSH 20 PUSH 15 CALL FWORD PTR SetXY In the preceding example, SetXY is actually a far pointer stored in memory that normally would contain the address of the SetXY function. The address is, however, really a call gate, not the real address of the SetXY function. Memory Management MMURTL uses all of the memory in your system and handles it as it as one contiguous ad- dress span. If you need a 2MB array, you simply allocate it. Chapter 3, “The Tasking Model,” and Chapter 4, “Interprocess Communications,” describe this in great detail.

Chapter 9 Application Programming S MMURTL has control of all physical or linear memory allocation and hands it out it as pro- grams ask for it. Application programs all reside at the one-gigabyte memory location inside of their own address space. The operating system resides at address 0. This doesn't change any programming techniques you normally use. What does change your techniques is the fact that you are working with a flat address space that is all referenced to one single selector. There are no far data pointers. This also means that a pointer to allocated memory will never start at 0. In fact, when an application allocates pages of memory, they will begin somewhere above one gigabyte (40000000h). MMURTL really doesn't provide memory management in the sense that compilers and lan- guage systems provide a heap or an area that is managed and cleaned up for the caller. MMURTL is a paged memory system. MMURTL hands out (allocates) pages of memory as they are re- quested, and returns them to the pool of free pages when they are turned in (deallocated). Heap- management functions that allow you to allocate less than 4K at a time should be provided by your high-level language. The library code for the high-level language will allocate pages and manage them for you. This would let you allocate 30 or 40 bytes at a time for dynamic struc- tures such as linked lists (for example, malloc( ) in C). A MMURTL application considers itself to be the only thing running on the system aside from the operating system and device drivers. Your application sees the memory map described in Table 9.2. Table 9.2. Basic memory map. Address Span Memory Usage 7FFFFFFFh Top of user address space 40000000h 3FFFFFFFh Application base (1GB) Top of OS address space OOOOOOOOh OS base address Even if six applications are running at once, they all see this same “virtual memory map” of the system. The operating system and device drivers reside in low linear memory (0), while all applications and system services begin at the one-gigabyte mark. Paged virtual memory is imple- mented with the paging capabilities of the 386/486 processor. Multiple applications all load and run at what seems to be the same address range, which logically looks like all programs are loaded parallel to one another. The paging features of the 386/486 enable you to assign physi- cal memory pages to any address ranges you like. Read up on memory management in Chap- ter 5 if you're really interested.

I
134 Part III ■ MMURTL-Specific Details The operating system provides three basic calls for application memory management. They are as follows: AllocPage(nPages, ppMemRet): dError DeAllocPage(pMem, dnPages): dError QueryPages(pdnPagesRet) : dError When your program needs a page of memory, you allocate it. When you are done with it, you should deallocate it. There are cleanup routines in the operating system that will deallocate it for you if your program exits without doing so, but remember, it's bad manners not to clean up after yourself. MMURTL is kind of like your mother. She may pick up your dirty socks, but she would much rather that you did so yourself. Memory-page allocation routines always return a pointer to the new memory. The address of the pointer is always a multiple of the size of a page (modulo 4096). The memory allocation routines use a first fit algorithm in your memory space. It is up to you to manage your own addressable memory space. Your program can address up to 1 GB of space. Of course, this doesn't mean you can allocate a gigabyte of memory, but pointer arithmetic may get you into trouble if you allocate and deallocate often and lose track of what your active linear addresses are. The high-level language allocation routine may help you out with this, but I have to address the issues faced by those that write the library allocation routines for the languages. Consider the following scenario:

  1. Your program takes exactly 1MB before you allocate.
  2. You allocate five pages. Address 40100000h is returned to you. You now have 20K at the 40 lOOOOOh address.
  3. Then you deallocate three of these pages at address 40100000h (12K). You are left with a hole in your addressable memory area. This is not a problem, as long as you know it can't be addressed.
  4. Now you allocate four pages. The memory- allocation algorithm can't fit it into the three-page hole you left, so it will return a pointer to four pages just above the original five you allocated first (address 40105000h). Now you have six pages beginning at 40103000h. If you attempt to address this “unallocated” area, a Page Fault will occur. This will terminate your application automatically. (Ouch!) Operating-System Protection MMURTL provides protection for the operating system and other jobs using the paging- hardware protection.

Chapter 9 ■ Application Programming The operating system code, data, and device drivers run and can only be accessed at the sys- tem-protection level (level 0), while system services and applications run at user-level protec- tion. The operating system prevents ill-behaved programs from intentionally or accidentally accessing another program's data or code (from “a pointer in the weeds”). It even prevents applications from executing code that doesn't belong to them, even if they know where it's located (that is, its physical or linear address). MMURTL does not, however, protect program- mers from themselves. This means that in a multitasking environment, a program that crashes and burns doesn't eat the rest of the machine in the process of croaking. In this case, the operating system acts more like a referee than a mother. Application Messaging The most basic applications in MMURTL may not even use messaging directly. You may not have to call an operating-system primitive-message routine throughout the execution of your entire program. However, when you write multithreaded applications (those with more than one task), you will probably need a way to synchronize their operations or to “queue up” events between the threads (tasks). This is where non-specific intertask messaging is employed. This is done with the SendMsg ( ) and WaitMsg ( ) calls. You may also require the use of a system service for which there isn't a direct procedural inter- face, in which case you will use Request ( ) and WaitMsg ( ) . The following messaging calls are provided by the operating system for applications: SendMsg (dExch, dMsgl , dMsg2): dError CheckMsg(dExch, pMsgsRet) : dError WaitMsg (dExch, pqMsgRet) :dError Request (pSvcName, wSvcCode, dRespExch, pRqHndlRet, npSend, pDatal , cbDatal , pData2, cbData2, dData0, dDatal , dData2 ) : dError Before you can use messaging, you will need an exchange where you can receive messages, or you will need to know of one you can send messages to. The following calls are provided to acquire exchanges and to return them to the operating system when you no longer need them: AllocExch(pdExchRet) : dError DeAllocExch(dExch) : dError Normally, applications will allocate all of the exchanges they require somewhere early in the program execution and return them (DeAllocExch) before exiting. Messaging is very powerful, but it can also be destructive (to the entire system). Memory- management protection provided by MMURTL will help some, but you can easily eat up all of the system resources and cause the entire system to fail. MMURTL is not a mind reader. For instance, accidentally sending messages in an endless loop to an exchange where no task is waiting to receive them causes the system to fail. This eats all of your link blocks. The operat- ing system is brought to its knees in a hurry.

y^n Pan III m MMURTL-Specific Details Starting a New Thread Some programs need the capability to concurrently execute two or three things, such as data communications, printing, and user interaction. A simple example would be keeping the time on the user interface updated no matter what else you are doing. In these cases, you can “spawn” a new task to handle the secondary functions while your main program goes about its business. The operating system provides the SpawnTask ( ) call, which gives you the ability to point to one of the functions in your program and turn it into a “new thread” of execution. This new func- tion will be scheduled for execution independently of your main program. Prior to calling SpawnTask ( ) , you need to allocate or provide memory from your data segment for the stack that the new task will use. Other than that, you basically have two programs run- ning in the same memory space. They can even execute the same procedures and functions (that is, share them). The stack you allocate should be at least 512 bytes (128 dwords) plus whatever memory you require for your task. You need to ensure you don't overrun this stack. If you do, only your program will be trashed, but you don't want that, either. You should be concerned about reentrancy if you have more than one task executing the same code. Reentrancy considerations include modifications to variables in your data segment (two tasks sharing variables), operating system messaging (sharing an exchange), and the allocation or use of system resources such as memory. Look at the code for the monitor (Monitor.c, provided with the operating-system source code) for a good example of spawning new tasks. Several tasks are spawned in the monitor. Job Control Block Applications may sometimes need to see the data in a Job Control Block. Utilities that display information about all of the jobs that are running may also need to see this data. The GetpJCB( ) call will provide a pointer to allow you to read values from a JCB. The pointer is read-only. Attempts to write directly to the JCB will cause your application to be terminated with a protection violation. In order to use the Get pJCB() call, you need to know your job number, which theGetJobNum( ) call will provide. Header files included with the source code have already defined the following structure for you. Listing 9.1 . JCB structure definition in C. struct JCBRec { long JobNum; char sbJobName(14] ; /* lstring */

Chapter 9 ■ Application Programming H char pJcbPD; / char pJcbCode; / unsigned long sJcbCode; /* char *pJcbData; /* unsigned long sJcbData; /* char *pJcbStack; /* unsigned long sJcbStack; /* char sbUserName[30] ; / char sbPath[70]; / char JcbExitRF[80]; / char JcbCmdLine[80] ; / char JcbSysIn[50] ; / char JcbSysOut[50]; / long ExitError; / char *pVidMem; / char *pVirtVid; / long CrntX; / long CrntY; long nCols; / long nLines; long VidMode; / long NormVid; / char fCursOn; / char fCursType; / unsigned char ScrlCnt; / char fVidPause; / long }; NextJCB; / Linear add of Job's PD */ Address of code segment */ Size of code segment */ Adresss of data segment */ Size of data segment */ Addrees of primary stack */ Size of primary stack */

  • User Name - LString */
  • current path (prefix) */
  • Exit Run file (if any) */
  • Command Line - LString */
  • std input - LString */
  • std output - LString */
  • Error Set by ExitJob */
  • Active video buffer */
  • Virtual Video Buffer */
  • Current cursor position */
  • Virtual Screen Size */
  • 0 = 80x25 VGA color text */
  • 7 = WhiteOnBlack */
  • 1 = Cursor is visible */
  • 0=UL, 1 = Block */
  • Count since last pause */
  • Full screen pause */
  • OS Uses to allocate JCBs */ The JCB structure is a total of 512 bytes, with unused portions padded. Only the active por- tions are shown in the preceding listing. All of the names and filenames (strings) are stored with the first byte containing the active size of the string and with the second byte (offset 1) actually holding the first byte of string data. To change data in the JCB, you must use the calls provided by the operating system. These include the following: SetJobName(pJobName, dcbJobName): dError SetExitJob(pFileName, dcbFileName) : dError SetPath(pPath, dcbPath): dError SetUserName(pUserName, dcbUserName) : dError SetSysIn(pFileName, dcbFileName): dError Some of the more commonly used information can be read from the JCB without defining the structure by using the following operating-system-provided calls: GetExitJob(pFileNameRet, pcbFileNameRet) : dError GetCmdLine(pCmdLineRet, pdcbCmdLineRet) : dError GetPath(dJobNum, pPathRet, pdcbPathRet) : dError GetUserName(pUserNameRet, pdcbUserNameRet) : dError

jjgl Part III m MMURTL-Specific Details Basic Keyboard Internal support is provided for the standard IBM PC AT-compatible 101-key keyboard or equivalent. The keyboard interface is a system service. Chapter 13, “Keyboard Service,” pro- vides complete details on the keyboard implementation. A system service allows concurrent access to system-wide resources for applications. The keyboard has a built-in device driver that the keyboard service accesses and controls. For most applications, the single procedural ReadKbd ( ) operating-system call will suffice. It provides access to all standard alphanumeric, editing, and special keys. This call returns a 32- bit value that contains the entire keyboard state. (All shifted states such as Alt and Ctrl are in- cluded.) There are tables in Chapter 13 to show all of the possible values that can be returned. The first of the two parameters to the ReadKbd ( ) call asks you to point to where the 32-bit keycode value will be returned, while the second parameter is a flag asking if you want to wait for a keystroke if one isn't available. Basic Video Each application and system service in MMURTL is assigned a virtual video buffer that is the same size as the video memory in standard VGA-color character operation. This is 4000 bytes (even though an entire page is allocated — 4096 bytes). The video calls built into the operating system provide TTY (teletype) as well as direct screen access (via PutVidChars and GetVidChar calls) for all applications. MMURTL has a basic character device driver built-in, which provides an array of calls to sup- port screen color, character placement, and cursor positioning. Positions on the screen are determined in x and y coordinates on a standard 80 x 25 matrix (80 across and 25 down). X is the position across a row and referenced from 0 to 79. Y is the position down the screen and referenced as 0 to 24. The character set used is the standard IBM PC internal font loaded from ROM when the sys- tem is booted. The colors for TTYOut, PutChars, and PutAttrs comprise 16 foreground colors, 8 background colors, and 1 bit for blinking. Even though the attribute is passed in as a dword, only the least significant byte is used. In this byte, the high nibble is the background, and low nibble is the foreground. The high bit of each is the intensity bit. Tables 9.3 and 9.4 describe the attributes. These names are also defined in standard header files included with the operating-system code and sample programs.

und colors (low nibble). Chapter 9 ■ Application Programming |
139 Table 9.3. Foregr Normal Binary Hex Intensity Bit Set Binary Hex Black 0000 OOh Gray 1000 08h Blue 0001 Olh Light blue 1001 09h Green 0010 02h Light green 1010 OAh Cyan 0011 03h Light cyan 1011 OBh Red 0100 04h Light red 1100 OCh Magenta 0101 05h Light magenta 1101 ODh Brown 0110 06h Yellow 1110 OEh White 0111 07h Bright white mi OFh Table 9.4. Background colors (high nibble). Normal Background Binary Hex Byte Black Blue Green Cyan Red Magenta Brown Gray 0000 OOh 0001 lOh 0010 20h 0011 30h 0100 40h 0101 50h 0110 60h 0111 70h To specify an attribute to one of the video calls, logically OR the foreground, background, and blink, if desired to form a single value. Look at the following coding example. (Hex values are shown for the attributes.) #def ine #define #define #def ine #define #define #define #def ine #def ine #define #define BLACK BLUE GREEN CYAN RED WHITE GRAY LTBLUE LTGREEN LTCYAN LTRED 0x00 1x07 0x02 0x03 0x04 0x07 0X08 0x09 0X0A 0X0B 0X0C

I
140 Part III ■ MMURTL-Specific Details #define BGBLACK 0x00 #define BGBLUE 1x70 #define BGGREEN 0x20 #define BGCYAN 0x30 dError = PutVidChars(0, 0, “This is a test. . . " , 17, BGBLUE | WHITE); Terminating Your Application The call ExitJob( ) is provided to end the execution of your program. The exit ( ) function in a high-level language library will call this function. It is best to deallocate all system resources before calling Exit Job ( ) . This includes any memory, exchanges, and other things allocated while you were running. You should also wait for re- sponses to any requests you made or system calls. MMURTL will clean up after you if you don't, but an application knows what it allocated, and from an overall system standpoint it is more efficient to do it yourself. MMURTL has to hunt for them. Remember, MMURTL is more like a referee on the football field. You will be terminated and sent to the “sidelines” if necessary. Any task in your job may call ExitJob( ). All tasks in your job will be shut down at that time and the application will be terminated. Replacing Your Application Your application has the option of “chaining” to another application. This is done by calling the Chain ( ) function. This terminates your application and loads another one that you specify. Some of the basic resources are saved for the new application, such as the job-control block and virtual video memory. You can pass information to the incoming application by using the SetCmdLineO function. Another option to chaining is to tell the operating system what application to run when you exit. This is done with the SetExitJob( ) call. You can specify the name of a run file to execute when your job exits. If no file is specified, all the resources are recovered from the job and it is terminated.

Powered by TurnKey Linux.