?? process.txt
字號:
Process Manager Routines
------------------------
The UCR Standard Library Process package provides a simple preemptive
multitasking package for 8086 assembly language programmers. It also
provides a coroutine package and support for semaphores.
First, *AND THIS IS VERY IMPORTANT*, this package only supports the
8086, 8088, 80188, 80186, and 80286 processors operating in real mode.
You will need to make some minor modifications to the process package if
you wish to support the 32-bit x86 processors. The current process manager
only saves 16-bit registers, not the 32-bit registers of the 80386 and later.
You will, however, find it a relatively minor task to go in and modify this
code to support the 386 and later processors. We will probably add support
for these processors at a later date when time allows.
Second, *THIS IS ALSO VERY IMPORTANT*, keep in mind that DOS, BIOS, and many
of the routines in the standard library ARE NOT REENTRANT. Two processes
executing at the (apparent) same time cannot both be executing DOS, BIOS, or
the same standard library routines. It is unlikely that DOS or BIOS will
ever be made reentrant, and you shouldn't ever expect the standard library
to be made reentrant (far too much work). The standard library provides
semaphore support through which you can control access to critical resources
including DOS, BIOS, and the UCR Standard Library. If you are unfamiliar
with terms like reentrancy, semaphores, synchronization, and deadlock, you
should probably pick up a good text on operating systems and familiarize
yourself with these terms before attempting to use this package.
This process package provides three facilities to your assembly language
programs: A preemptive multitasking process manager, coroutine support, and
semaphore support. There are six routines associated with the preemptive
multitasking system: PRCSINIT, PRCSQUIT, FORK, KILL, DIE, and YIELD. There
are three routines associated with the coroutines package: COINIT, COCALL,
and COCALLL (though you'll rarely refer to COCALLL directly). Finally, there
are two routines to support semaphores: WAITSEMAPH and RLSSEMAPH.
The PRCSINIT and PRCSQUIT routines initialize and deinitialize the interrupt
system for the multitasking system. You must call PRCSINIT prior to
executing any of the preemptive multitasking routines or any of the semaphore
routines (the semaphore routines make sense only in the context of preemptive
multitasking). This initializes various internal variables and patches the
INT 8 interrupt vector (timer interrupt) to point at an internal routine
in the process manager. You must call PRCSQUIT when you are done with the
preemptive multitasking system; certainly you must call it before your
program terminates *FOR ANY REASON*. If you do not call PRCSQUIT, the system
will probably crash shortly after you try anything else after returning to
DOS since the timer interrupt will still be calling the routine left in
memory when your program terminates.
The process manager patches into the 1/18th second clock on the PC. There-
fore, the system will automatically perform an context switch every 55ms
or so. If your application reprograms the timer chip, this may produce
unexpected results. This may be particularly bothersome if you are running
a TSR which plays with the timer chip. Absolutely no attempt was made to
make this code robust enough to work in all cases with other code which
ties into the timer interrupt. Most well-written code will work fine, but
there are not guarantees.
The FORK routine lets you spawn a new process. For each call to FORK your
code makes, the FORK routine returns twice- once as the parent process and
once as the child process. FORK returns process ID information in the AX
and BX registers so that the code immediately following the FORK can figure
out if it's the parent or child process. FORK provides the basic (and only!)
mechanism for starting a second process.
The KILL and DIE routines let you terminate a process. KILL lets one process
terminate some other process (generally a child process). DIE lets a
process terminate itself.
The YIELD routine gives up the current process' time slice and lets some other
process take over.
The semaphore routines, WaitSemaph and RlsSemaph, let you wait on a semaphore
or signal that semaphore, respectively. The PROCESS.A and PROCESS.A6
include files contain the definition of a semaphore type, it is
semaphore struc
SemaCnt dw 1
smaphrLst dd ?
endsmaphrlst dd ?
semaphore ends
The only field you should ever play around with is the SemaCnt field. This
value is the number of processes which are allowed to be in the critical
region controlled by the semaphore at one time. For most mutual exclusion
problems, this value should always be one. Do not modify this value once
the program starts running. The process package increments and decrements
this number to keep track of the number of processes waiting to use a
resource. If you want to allow two processes to share a resource at the
same time, you should declare your semaphore variable as follows:
MySemaPh semaphore <2>
You execute the WaitSemaPh routine to see if a semaphore is currently busy.
When you get back from the WaitSemaPh call, the resource protected by the
semaphore is exclusively yours until you execute the RlsSemaPh routine.
Note that when you call the WaitSemaPh routine, the specified resource may
already be in use, in which case your process will be suspended until the
resource is freed (and anyone waiting in line ahead of you has had their
shot at the resource). If you do not call the RlsSemaPh routine to free
the semaphore, any other process waiting on that resource will wait
indefinitely. Also note that if you call WaitSemaPh twice on a semaphore
without releasing it inbetween, your process and any other process which
waits on that resource will deadlock.
While semaphores solve a large number of synchronization and mutual exclusion
problems, their primary use in the UCR Standard Library is to prevent re-
entrancy problems in DOS, BIOS, and the Standard Library itself. For example,
if you have two processes which print values to the display, attempting to
run both processes concurrently will crash the system if they both attempt
to print at the same time (since this will cause DOS to be reentered). A
simple solution is to use a DOS semaphore as follows:
In the data segment:
DOS semaphore {}
In Process 1:
lesi DOS
WaitSemaPh
print
db "Printed from process #1.",cr,lf,0
lesi DOS
RlsSemaPh
In Process 2:
lesi DOS
WaitSemaPh
print
db "Printed from process #2.",cr,lf,0
lesi DOS
RlsSemaPh
Semaphore guarantee mutual exclusion between the WaitSemaPh and RlsSemaPh
calls (for a particular semaphore variable, DOS in this case). Hence, once
process #1 enters its *CRITICAL REGION* by executing the WaitSemaPh call,
any attempt by process two to enter its critical region will cause process
two to suspend execution until process one executes the RlsSemaPh routine.
Coroutines provide "simulated" multitasking where the processes themselves
determine when to perform a context switch. This is quite similar to the
"cooperative multitasking" systems provided by Apple, Microsoft, and others
("cooperative multitasking" is a hyped up term to hide the fact that their
systems provide only multiprogramming, not multitasking). There are many
advantages and disadvantages to coroutines vs. multitasking. First of all,
reentrancy problems do not exist in a system using coroutines. Since you
control when one process switches to another, you can make sure that such
context switches do not occur in critical regions. Another advantage to
coroutines is that the processes themselves can determine which other process
gets the next access to the CPU. Finally, when a coroutine is executing,
it gets full access to the CPU to handle a time-critical operation without
fear of being preempted. On the other hand, poorly designed coroutines
provide a very crude approximation to multitasking and may actually hurt
the overall performance of the system.
The UCR Standard Library provides three routines to support coroutines:
COINIT, COCALL, and COCALLL. Generally, you'll only see COINIT and COCALL
in a program, the standard library automatically generates COCALLL calls
for certain types of COCALL statements. The COINIT routine initializes the
coroutine package and creates a process control block (PCB) for the currently
active routine. COCALL switches context to some other process. When one
process COCALLs another and that second process COCALLs the first, the first
process continues execution immediately after the first COCALL instruction
(so it behaves more like a return than a call). In general, you should not
think of COCALL as a "call" but rather as a "switch to some other process."
You may have coroutines and multitasking active at the same time, but you
should not make a COCALL to a process which is being time-sliced by the
multitasking system. I won't guarantee that this *won't* work, but it
seems sufficiently weird that something is bound to go wrong.
For those who are interested, the coroutine and multitasking packages
maintain the state of a process in a process control block (PCB) which
is the following structure:
pcb struc
NextProc dd ?
regsp dw ?
regss dw ?
regip dw ?
regcs dw ?
regax dw ?
regbx dw ?
regcx dw ?
regdx dw ?
regsi dw ?
regdi dw ?
regbp dw ?
regds dw ?
reges dw ?
regflags dw ?
PrcsID dw ?
StartingTime dd ?
StartingDate dd ?
CPUTime dd ?
pcb ends
As mentioned earlier, this code does not maintain the full state of the
80386 and later processors since it only saves the 16-bit register set.
If you like, you can easily change the definition of the PCB, and all the
code in the PROCESS.ASM file which refers to the PCB, and support full
32-bit operation. As usual, you should rarely, if ever, play around with
the internal fields of a PCB. That is for the process manager to do and you
could mess things up pretty back if you're not careful.
Routine: prcsinit
-----------------
Category: Processes
Registers on entry: None
Registers on return: None
Flags affected: None
Example of Usage:
prcsinit
Description:
Prcsinit initializes the process manager. Note that if you make a call to
the process manager, you *MUST* make a call to prcsquit before your program
quits. Failure to do so will crash the system in short order. Note that
you must even handle the case where the user types control-C or encounters
a critical error and aborts back to DOS.
This routine patches into the timer interrupt vector (INT 08) and may not
work properly with code in the system which is also messing around with the
timer interrupt.
Include: stdlib.a or process.a
Routine: prcsquit
-----------------
Category: Processes
Registers on entry: None
Registers on return: None
Flags affected: None
Example of Usage:
prcsquit
Description:
Prcsquit deinitializes the process manager. It detaches the timer interrupt
from the internal routine and performs various other clean up chores. You
must call this routine before your program terminates if you've called
prcsinit somewhere in your program.
Note that you cannot call prcsquit twice in a row, although you can call
the prcsinit/prcsquit combinations as many times as you like in your code.
Include: stdlib.a or process.a
Routine: fork
-------------
Category: Processes
Registers on entry: ES:DI- Points at a PCB to hold the process info
for the new process. The regss and regsp
fields of this PCB must be initialized to
the top of a new stack for the new process.
Registers on return: AX- <Parent process> Returned containing zero.
<Child process> Child process ID.
BX- <Parent process> Child's process ID.
<Child process> Returned containing zero.
Flags affected: None
Example of Usage:
ChildPCB pcb {0, offset endstk, seg endstk}
.
.
.
lesi ChildPCB
fork
cmp ax, 0
jne DoChildProcess
<Parent process continues here>
.
.
.
ChildsStack db 1024 dup (?)
EndStk dw 0
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -