- Write a library for event handling. Make up a
definition
for "event" (e.g., a C++ class that inherits from "MyEvent" and defines
a virtual "run" method), clearly
define what an event is and when it's supposed to execute, and
implement a library to execute the events. This library could be
useful to network servers, simulators, and anywhere many different
flows of control are required. Design decisions
include:
- What triggers an event's execution? A flag, which is later polled? A subroutine call into the library?
- Which event runs when more than one event is ready to execute?
First-in, first-out is easy to define and implement, but many
applications need per-event prioritization.
- After an event runs, is it over? Or can it get called again later somehow?
- Complete example code:
#include "myevent.h"
class sequencer : public myevent_class {
public:
const char *what; /* what we're doing */
int i, n; /* current step, and number of steps */
sequencer(const char *what_,int n_) {what=what_; i=0; n=n_;}
virtual void execute(void) {
i++;
printf("%d: step %d of %d\n",what,i,n); /* or real code here... */
if (i<n) myevent_do(this); /* ask for another execution */
}
};
int main() {
sequencer s1("foo",6), s2("bar",3);
myevent_do(&s1); /* will run s1.execute() once during loop */
myevent_do(&s2); /* will run s2.execute() once during loop */
myevent_loop(); /* keep processing events until there are no more */
}
- Write
a (cross-platform?) kernel thread library, that can at least create and
coordinate threads. You may make the
library work on Linux (using pthreads, see pthread_create) and/or
Windows (see CreateThread). This library would be useful to
anyone writing cross-platform threaded code. The biggest design
decision is to choose a set of synchronization
primitives: you may choose to implement locks (mutual exclusion routines).
Very simple example code:
#include "mythread.h"
void function1(void *myData) { ... }
void function2(void *myData) { ... }
int main() {
void *myData=...;
mythread_start(function1,myData);
mythread_start(function2,myData);
... /* function1 and function2 should now both be running */
}
- Write a
(cross-platform?) "start this other program alongside me" library.
This is only slightly different from the standard "system" call, which
suspends the caller, in that both the old and new programs would run
simultaniously. This library would be useful to, for example,
start a long-running network file copy via ssh, while allowing the
calling program to continue running. You may make the library
work on UNIX (using fork) and/or Windows (using CreateProcess).
Decide on how to handle command-line arguments, input/output, and how
to deal with the termination of either the creating or created program.
Complete example code:
#include "myprocess.h"
int main() {
myprocess_start("program1 argument1");
myprocess_start("program2 argument2");
... /* program1 and program2 should now both be running */
}
- Write
a deadlock-detecting lock library. The interface could be a
wrapper around the lock/unlock routines of some thread library (such as
pthreads). Whenever aquiring a lock would lead to deadlock,
instead of
hanging, your library must print an error message and abort. Be sure to
handle all possible types of deadlock, including locking a lock
you already hold (self-dependency), and multi-way deadlock (where A
waits for B, B waits for C, and C waits for A). This library
would be useful if you suspect your multithreaded code may have a
deadlock problem. You can restrict
yourself to only one type of synchronization primitive, such as locks,
and ignore other sorts of synchronization (flags, files, etc.).
Complete example code:
#include "mylock.h"
int main() {
mylock_t l; /* a lock */
mylock_create(&l); /* initialize the lock */
mylock_lock(&l); /* lock it */
mylock_lock(&l); /* whoops! Locked it twice! Should cause error. */
}
- Write a library to dump out the contents of some executable
format. For example, you might choose to work with Windows EXE PE
format executables, or UNIX 32-bit ELF executables. Make the
library be able to at least print out the approximate size of the
executable program code (machine code), and the size of the initialized
global data (like strings). This library would be useful to
estimate the memory cost of loading a program. This project will mostly
consist of reading the executable format documentation and doing
testing.
Example program run:
C:/> dir
someprogram.exe mydump.exe
C:/> mydump someprogram.exe
Contents of executable someprogram.exe:
- 7132 bytes of executable code
- 452 bytes of initialized global data.
- Write a segfault response library. Your library should provide a way to,
when a program accesses an out-of-bounds memory address, execute some
client code that is passed at least the out-of-bounds address.
This library would be useful for debugging programs where a regular
debugger can't be used, such as on a large parallel machine or in a
shipped application. For UNIX, see siginfo & signal(SIGSEGV);
for Windows, see SetUnhandledExceptionFilter.
Complete example code:
#include "myfault.h"
void handleFault(void *theBadAddress) {
fprintf(stderr,"Tried to access bad memory at %p\n",theBadAddress);
/* could, e.g., save really important file here,
or send error report out over network. */
exit(1);
}
void doStuff(void) {
int i, len=1000000;
int *array=(int *)malloc(len); /* caution! len bytes, not len ints! */
printf("Allocated array at %p\n",array);
for (i=0;i<len;i++) array[i]=0; /* will hit bad address */
printf("Done.\n");
}
int main() {
myfault_handler(handleFault); /* call handleFault on segfault */
doStuff();
}
- Write a library to allow easy access to the machine's timer
interrupt. For example, the library could provide a routine that
will execute some client code after the specified number of
microseconds have elapsed, without just waiting for that many
microseconds. This could be a wrapper around the UNIX
asynchronous signal interface setitimer & signal(SIGALRM) or the Windows equivalents.
Example code:
#include "mytimer.h"
class timeHandler : public mytimer_class {
public:
int stuffDone;
/* Runs after 2 seconds have passed. */
virtual void execute(void) {
if (!stuffDone) {
printf("This stuff is taking too long! Goodbye!\n");
exit(1);
}
}
};
int main() {
timeHandler h;
h.stuffDone=0;
mytimer_callafter(h,2.0); /* call h.execute after 2 seconds */
doStuff(); /* if this takes too long, exit! */
h.stuffDone=1;
...
}
- Write a Linux or Windows kernel module to do something in kernel
space. This isn't a library, but it's still got to have an interface
(some way to call the module) and some sort of functionality (even if
it's just printk("hello from the kernel (%d)!",some_user_data)).
- Write your own implementation of threads. For example,
threads can be created and switched from inside ordinary code using the
SYSV Unix makecontext/swapcontext routines (I also have a Windows
version of these routines available if you need it); or for the truly
hardcore, your own context-switching assembly code. Threads can
also be simulated via a big "switch (program_counter)" statement in
C/C++. Such a library would be useful to avoid some of the silly
limitations of kernel threads--for example, both Linux and Windows have
hard limits on the number of kernel threads you can create.
You'll have to decide exactly how threads are created and switched, but
you can ignore synchronization.
Complete example code:
#include "mythread.h"
class sequencer : public myevent_class {
public:
const char *what; /* what we're doing */
int n; /* number of steps it takes to do it */
sequencer(const char *what_,int n_) {what=what_; n=n_;}
virtual void execute(void) {
for (i=0;i<n;i++) {
printf("%d: step %d of %d\n",what,i,n); /* or real code here... */
mythread_schedule(); /* run other threads if possible */
}
}
};
int main() {
sequencer s1("foo",6), s2("bar",3);
mythread_create(&s1); /* will run s1.execute() once */
mythread_create(&s2); /* will run s2.execute() once */
mythread_run(); /* keep running threads until they all exit */
}
- Write
a library to list the files in a directory--a directory traversal
library. On UNIX, you can walk a directory using the
opendir/readdir/closedir routines (in dirent.h). Under Windows,
you can walk them with _findfirst, _findnext, _findclose (in io.h).
You'll have to decide if you want to report just files, or files and
directories--ideally, you should be able to ask for both.