prcsinit
, prcsquit
, fork
, die
, kill
, and yield
. These functions let you initialize and shut down the threads system, start new processes, terminate processes, and voluntarily pass the CPU off to another process.prcsinit
and prcsquit
functions let you initialize and shutdown the system. The prcsinit
call prepares the threads package. You must call this routine before executing any of the other five process routines. The prcsquit
function shuts down the threads system in preparation for program termination. Prcsinit
patches into the timer interrupt (interrupt 8). Prcsquit
restores the interrupt 8 vector. It is very important that you call prcsquit
before your program returns to DOS. Failure to do so will leave the int 8 vector pointing off into memory which may cause the system to crash when DOS loads the next program. Your program must patch the break and critical error exception vectors to ensure that you call prcsquit
in the event of abnormal program termination. Failure to do so may crash the system if the user terminates the program with ctrl-break or an abort on an I/O error. Prcsinit
and prcsquit
do not require any parameters, nor do they return any values.fork
call spawns a new process. On entry, es:di
must point at a pcb for the new process. The regss
and regsp
fields of the pcb
must contain the address of the top of the stack area for this new process. The fork
call fills in the other fields of the pcb
(including cs:ip)/fork
, the fork
routine returns twice, once for each thread of execution. The parent process typically returns first, but this is not certain; the child process is usually the second return from the fork
call. To differentiate the two calls, fork
returns two process identifiers (PIDs) in the ax
and bx
registers. For the parent process, fork
returns with ax
containing zero and bx
containing the PID of the child process. For the child process, fork
returns with ax
containing the child's PID and bx
containing zero. Note that both threads return and continuing executing the same code after the call to fork
. If you want the child and parent processes to take separate paths, you would execute code like the following:lesi NewPCB ;Assume regss/regsp are initialized. fork test ax, ax ;Parent PID is zero at this point. je ParentProcess ;Go elsewhere if parent process. ; Child process continues execution here
The parent process should save the child's PID. You can use the PID to terminate a process at some later time.
It is important to repeat that you must initialize the regss
and regsp
fields in the pcb
before calling fork
. You must allocate storage for a stack (dynamically or statically) and point ss:sp
at the last word of this stack area. Once you call fork
, the process package uses whatever value that happens to be in the regss
and regsp
fields. If you have not initialized these values, they will probably contain zero and when the process starts it will wipe out the data at address 0:FFFE. This may crash the system at one point or another.
The die
call kills the current process. If there are multiple processes running, this call transfers control to some other processes waiting to run. If the current process is the only process on the system's run queue, then this call will crash the system.
The kill
call lets one process terminate another. Typically, a parent process will use this call to terminate a child process. To kill a process, simply load the ax
register with the PID of the process you want to terminate and then call kill
. If a process supplies its own PID to the kill
function, the process terminates itself (that is, this is equivalent to a die
call). If there is only one process in the run queue and that process kills itself, the system will crash.
The last multitasking management routine in the process package is the yield
call. Yield
voluntarily gives up the CPU. This is a direct call to the dispatcher, that will switch to another task in the run queue. Control returns after the yield
call when the next time slice is given to this process. If the current process is the only one in the queue, yield
immediately returns. You would normally use the yield
call to free up the CPU between long I/O operations (like waiting for a keypress). This would allow other tasks to get maximum use of the CPU while your process is just spinning in a loop waiting for some I/O operation to complete.
The Standard Library multitasking routines only work with the 16 bit register set of the 80x86 family. Like the coroutine package, you will need to modify the pcb and the dispatcher code if you want to support the 32 bit register set of the 80386 and later processors. This task is relatively simple and the code is quite similar to that appearing in the section on coroutines; so there is no need to present the solution here.
cli
) may not solve the reentrancy problem. Consider the following code:cli ;Prevent reentrancy. mov ah, 3Eh ;DOS close call. mov bx, Handle int 21h sti ;Turn interrupts back on.
This code will not prevent DOS from being reentered because DOS (and BIOS) turn the interrupts back on! There is a solution to this problem, but it's not by using cli
and sti
.