**************************************************************************** * * * Pascal Multi Process Unit written by Dieter Pawelczak * * * * About the Source Code * * * * (c) 1995,1996 by Dieter Pawelczak, Fasanenweg 41, 85540 Haar, Germany * * * * Third Release Process Unit Version 3.1 * * * **************************************************************************** This is a short manual, about how the process unit works. 1. Program Attributes and Interrupts Each program has several attributes: The register state, the current program counter, the stack pointer, etc. If you can save all attributes of a program, you can stop a program and continue the program without any loss of information. The CPU (Central Processing Unit, the heart of our PC), is doing such a process many times a second: Each time, we press a key, the current program is interrupted by a request from the keyboard controller, and the key is stored as an ascii code in the keyboard buffer. It doesn't matter, in which state of the program, the key had been pressed. Maybe it's been exactly in the same moment, when the keyboard is checked by the program, or the program is doing a floppy operation in that moment. No matter when the key is pressed, our processor will notice the key stroke. The program is interrupted by the processor, and continued at the same position. The interrupt process is a service, the CPU provides. There are several events, which forces the CPU to do an interrupt: Keyboard, Disk Drive, Video, Mouse, etc. Now imagine, we could use the interrupt functionality to change the attributes of a program. Say, we have two programs. When an interrupt occurs, the processor saves all the attributes of the first program and restores all the attributes of the second program. Now, the program pointer points to the second program, the register contain the register values of the second program and so on. After the interrupt, the CPU will execute the second program. With the next interrupt, the process is done vice versa, and the first program continues. Imagine, the interrupt is executed very often per second, then we couldn't say exactly, which program actually is executed. It will look just as if both programs are running at the same time... We shouldn't look only at two different programs. Generally, we could say, that different procedures of one program, or even different procedures of different programs can be executed parallely. The only necessary condition for this is, that all attributes of the program and the current procedure are safed and restored. What are the program attributes? - Program Data Segment, - Program Code Segment, - Program Stack, - PSP (*) - Environment (**) And what are the procedure attributes? - Current Program Counter (IP) - Current Code Segment - Current Data Segment - Current Extra Segment (***) - All general processor register - Procedure Data (****) - Current Stack Segment - Current FPU Stack (*****) (*) PSP is useually reachable via Data Segment, or Code Segment, this attribute is therefore optional (**) Environment reachable via PSP, therefore optional (***) Extra Segment, for example in TP String operations, WITH operator, etc. (****) Procedure Data: local data resident in the current stack segment, (*****) FPU Stack here not supported, because the FPU save and store functions cost a lot of CPU time... These data must be saved and restored for each procedure with every task switch. The concept of the PROCESS unit is to store all data in the stack segment and to provide for each procedure a different stack segment. Every thing what need to be done to perform a task switch is to save all attributes on stack, swap the stack segment and restore all attributes on the stack. To create a new task, we must allocate a new stack and store all initial values on the stack. Furthermore, we need an array, or a list which stores all task stack addresses and some further attributes of a task. A task can have the following attributes: existing, waiting, stopped, running and killed. 2. The Timer Interrupt We need an interrupt event, which frequently occurs, and which is independend from the program action. Fortunately, the PC provides a timer interrupt, which is called about 18.2 times a second. If we could use this interrupt, we could switch about every 55ms a task. The only problem is, that we need to call the original interrupt handler, because otherwise, some basic functions (for example floppy operations) won't work. So before the new interrupt handler returns to the new task, the old interrupt handler is called. We can alter the interrupt occurence frequency by reprogramming the timer interrupt. The problem is, that the DOS clock will be fast, and some other functions chained with the timer interrupt will change their performance. For this reason, we simply allow only frequency changes with integer values, for example: two times as fast, three times etc. Our own interrupt handler can now call the original handler only every second, third time etc. As result, the original handler is called 18.2 times a second, but the task switch is done 36.4, 54.6 times a second, etc. 3. The multitasking kernel for multiprogramming If we want to allow multitasking inside a program, we can use a local multitasking function. On the other hand, if we want to allow multitasking generally, we need an application independend multitasking kernel. Exactly this is done by the PROCESS unit. All global data of the multitasking kernel (all task stack addresses, task flags, etc.) are addressed via a pointer. Every program can get the pointer via the free interrupt $7E. The first program, which enables multitasking (and this program is certainly run in singletasking mode), will allocate the multitasking kernel and install the interrupt service routine. Now, in multitasking mode, other programs can access the multitasking kernel via the interrupt $7e. It doesn't matter, which service routine accesses the kernel. Usually it will be another copy of the PROCESS unit. The important thing is, that all programs which change the kernel, are working with the same kernel model. For this reason the kernel has stored a kernel version number. Each program, which addresses the kernel, has to check at first, if the program knows the kernel version. If not, the program should not access the kernel. The PROCESS unit compares the kernel version with the unit version. If there's a difference, the program aborts with a version conflict error. 4. FPU support Currently, the unit doesn't support the FPU. Mainly, because storing and restoring the FPU status takes quite some time. You can use the FPU nevertheless, if you don't allow a task switch during an FPU calculation. You can add real support, by adding to the transfer, delay_task and kill_task procedure, after the mov ds,ax inline assembler instruction: FSAVE SS:[8] and immediately before the iret / end instruction FRSTOR SS:[8]. This will store and restore the FPU status at the beginning of the stack segment for each task. (Note, that a pointer has an offset of 0000 or 0008 after getmem in TP). In create_process, create_exe, you'll have to add the following sequence, to store an initial FPU status (inside the WITH statement, which sets the task properties): asm mov ax,stackseg mov es,ax FSAVE es:[8] end; These instructions are marked inside a (* comment *) inside the original PROCESS unit. email: et_514@eikon.e-technik.tu-muenchen.de dieterp@bigfoot.fe www: http://www.eikon.e-technik.tu-muenchen.de/~et_514/ http://www.geocities.com/SiliconValley/Bay/9159/