C INTERFACE
int lwp_init(int irq,int speed)
This function hooks all the stuff that needs to be hooked. After you call
this function, main() is operating as a task, but since it is the only
task you won't notice. Both timer IRQs are supported (0 and 8). IRQ8 only
supports certain speeds (2hz, 4hz, 8hz, 16hz...8192hz, see lwp.h for more).
IRQ0 is programmable, and any speed can be implemented. 10hz < speed
< 1024hz is recommended. This function automagically cleans up after
itself when your program exits.
Returns 0 if something went wrong, != 0 if any problem was found.
NOTE: IRQ0 doesn't work under Win 95. I haven't tested it under
OS/2 or Win 3.1. I think Windows hooks it.
Example:
if(lwp_init(8,RTC128))
{
... do your stuff ...
} else printf("Error!\n");
int lwp_spawn(void (*proc)(void *), void * arg,
int stack_size,int priority)
This function spawns off other tasks. Proc is the name of a function of
type void proc(void *). Stack size is used to create a stack that
is used by your proc. It won't spawn if stack_size < 255. I'd suggest
at least 4k, just to be safe. Your proc will lwp_kill
itself when it returns, so there is no need to worry about threads joining.
Priority specifies the number of consecutive time slices the thread
will have. Any number >0 will fit.
Returns the PID of the new process.
Example:
void proc(void *arg)
{
printf("My proc is %s!\n", (char *) arg );
}
main()
{
lwp_init(0,100);
lwp_spawn(proc, "kewl", 4096,1);
/* do some stuff...*/
}
int lwp_kill(int lwpid)
This function kills a process. Simple. Pass it the pid of the process and
blahmo, it's dead.
A thread can safely commit suicide by calling lwp_kill(lwp_getpid())!
NOTE: main() can't be killed!
NOTE: Processes that return automagically kill themselves.
Returns 0 if couldn't kill thread, 1 if thread was correctly
killed
int lwp_getpid(void)
This returns the current process's pid, useful in killing the current process.
Example:
void proc1()
{
...do stuff...
lwp_kill(lwp_getpid());
/* never returns */
}
void lwp_thread_disable(void)
This function disables task switching. It's mostly used to fix non re-entrant
functions like printf, malloc, etc.
void lwp_thread_enable(void)
This function re-enables task switching after a previous lwp_thread_disable().
Example:
lwp_thread_disable();
printf("foo = %d\n", foo);
lwp_thread_enable();
Take a look at the lwpstdio.h, lwpconio.h, and lwppc.h that are included
with this distribution. It takes care of SOME of libc's non-reentrant functions.
You don't need to wrap functions like printf with thread_disable and thread
enable, because it's automagically done in the included header files. If
there is a function you are using that isn't in one of the above header
files and you aren't sure that it is re-entrant, wrap it with lwp_thread_disable
and lwp_thread_enable, just to be safe.
void lwp_yield(void)
This function causes a task switch to happen. the main purpose of this
function is to help task switches happen in tight loops with a lot of lwp_disable/enable
going on, where the time window for PDMLWP to switch the threads might
be VERY small. Due to suble aspects of DPMI interrupt handing, a program
cannot be interrupted when it doesn't touch any of its data. So a very
tight loop - or one with a lot of system calls (which disable PDMLWP) -
might effectively stop multithreading. In the following example, the time
window is reduced to virtually a few assembler instructions!
while(!kbhit())
{
wait++; /* Waste time...*/
}
To avoid problems, this should become:
while(!kbhit()) /* Locks the processor! */
{
wait++; /* Waste time...*/
lwp_yield(); /* Give the other threads a chance too... */
}
void lwp_atkill(void (*proc)(void))
Sets up per-thread exit functions, in a LIFO stack. This function will
be called by lwp_tcp when your thread is killed, it is either when it returns
or when it is lwp_kill'ed.
NOTE: The exit function is called in an essentially non-reentrant
way! You can't use any PDMLWP function from within the exit functions.DOING
SO WOULD HANG THE SYSTEM! But you can call system routines, as multithreading
is disabled.
Example:
void exitproca(void)
{
printf("Finished 2\n");
}
void exitprocb(void)
{
printf("Finished 1\n");
}
void thread(void *unused)
{
...
lwp_atkill(exitproca);
lwp_atkill(exitprocb);
...
return ;
}
Output:
Finished 1
Finished 2
void lwp_sleep(unsigned int secs,unsigned short
msecs)
Puts to bed your task for a given time, given at the precision of 1 thousandth
of a second (the real resolution is about 50ms, because of inherent limitations
of libc's ftime).
IMPORTANT: While the thread "sleeps", it doesn't eat up cycles.
It simply won't receive time slices.
Example:
void periodic_check(void)
{
while(!finished)
{
lwp_sleep(60*15,0); /* Wait 15 minutes */
autosave();
}
}
This task wakes up every 15 minutes, and auto-saves the situation.
void lwp_setuserptr(void *usrdata)
void *lwp_getuserptr(void)
These two member functions allow the user to set a per-thread user pointer,
that can be read/written by every single thread. This lets you write code
accessing different data depending on the thread it executes under, without
having to check the PIDs to find what to operate on. This feature may be
used for instance to write down single code/multiple data threads.
void lwp_wait_true(volatile int *what)
void lwp_wait_false(volatile int *what)
These two functions play a very important role in multitasking: they allow
a thread to wait for an integer to become true or false, without consuming
time slices. Their most immediate use is in the implementation of a gate,
i.e. of a "door" that can be opened or closed by another process.
Example:
volatile int trigger = 0;
void triggered_task(void)
{
lwp_wait_true(&trigger);
... Does what it has to do...
}
void main()
{
lwp_spawn(triggered_task,8192);
...Waits for some event...
trigger = 1; // Here it is. From this moment, the triggered_task
// can proceed!
... Does the rest of its stuff...
}
void lwp_pulse_true(volatile int *what)
void lwp_pulse_false(volatile int *what)
These two functions are useful to implement event raisers: they let any
thread waiting for "*what" to become true/false pass, without actually
changing the value of *what! It is, they'll set all the threads that
are waiting free, but they'll stop any other thread calling lwp_wait_true/false
afterwards.
void lwp_wait_semaphore(lwp_semaphore *sema)
void lwp_init_semaphore(lwp_semaphore *sema)
int lwp_release_semaphore(lwp_semaphore *sema)
These are perhaps the most important functions in the package. They allow
a process to wait for a Mutex semaphore, i.e. for a semaphore that can
be owned by just one thread at a time. Examples of such situations
are resources such as the keyboard, the screen: only one process at a time
may own one of these resources. Another kind of "resource" that needs to
be locked is data: it is useful to implement locks on particular
memory areas that cannot be safely accessed by two threads at the same
time.
Example:
lwp_semaphore sema;
int count = 0;
void thread(void)
{
... Does some stuff ...
lwp_wait_semaphore(&sema); //Wait until semaphore is free
count++; // Do something...
lwp_release_semaphore(&sema); // Make the semaphore free again.
... Does some other stuff ...
}
void lwp_set_priority(unsigned priority)
Sets the priority at the value you specify for it. Don't rely on this function
to protect the non-reentrant parts of your program: the priority counter
is set only at next task switch.
unsigned lwp_get_priority(void)
Returns the priority count, i.e. the number of consecutive time slices
the thread will receive.
Standard Disclaimer
There are absolutely no guarantees, expressed or implied, on anything that
you find in this document. I cannot be held responsible for anything that
results from the use or misuse of this document.
Paolo De Marino (paolodemarino@usa.net)
Via Donizetti 1/E
80127 Naples
Italy