- Write a network server that speaks some existing protocol, or make up a new protocol of your own.
- Write a program that extracts some sort of information from an existing media file format, like .wmv or .avi.
- Use threads to accomplish some sort of real work (e.g., make a file lowercase), or to make any existing program faster.
- Write a GUI program in any language, even easy ones like Java or GLUI.
- Implement a program that uses memory-mapped files in some way (google "mmap" or "MapViewOfFile").
- Write a "file system info" program, to extract some useful
piece of information from the on-disk layout of a filesystem of your
choice. For example, you could walk the FAT table on a FAT
volume, print the total used inodes in an ext2 volume, or display the
size of an HFS volume. I can provide binary files with images of
these filesystems for you to test your code, or just open
"\\.\PhysicalDrive0" to read your own hard disk. (Google: "FAT
filesystem
header", "ext2 superblock", "HFS volume header")
- 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 must make the library
work on both UNIX (using fork) and 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 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 "plugin" library, to take a dynamically linked library
file name, load the library, and call one of its routines. This
exact interface is used by, e.g., browser plugins. (Google: "dlopen", "LoadLibrary").
- Implement
your own dynamic allocation routines. These can use any style you
like: first-fit like malloc, tree-based, mmap-based, etc. You
might do this to add a layer of error checking to find memory leaks,
detect heap corruption, etc.
- Write any Linux or Windows kernel module, to do something (even "Hello, World!") in kernel
space. You've got to figure out how to get data into the kernel module, how to do your processing, and how to get out.
- Implement a device driver for any operating system. If you
choose to write a user-space driver, it must talk to real hardware
(e.g., a USB device--google "libusb"). If you choose to write a
kernel-space driver, the "device
driver" can actually generate hardcoded or random data, but it must be
accessible
from outside the kernel (google "linux character device").
- Write
a cross-platform kernel thread library, that can at least create and
coordinate threads. You must make the
library work on Linux (using pthreads, see pthread_create) and
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 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. If you're really ambitions,
handle all possible types of deadlock, in addition to locking a lock
you already hold (self-dependency), check for 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 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 */
}
- Or, make up your own! Just make sure it's do-able within a month...