Hi,
Mercury1964 wrote:What's a good way of noting what the process is waiting for? An enum coupled with an int or two for data (for instance, STATE_SLEEPING, ticks elapsed since sleep(), ticks to sleep() for)? A separate taskState struct?
A simple enum can be fine - it's mostly only used for getting information (e.g. so kernel can tell a debugger or something the state of each thread in a process) and for sanity checking (e.g. code that unblocks a task can check if the task is actually blocked for the right reason and panic if something is wrong).
More interesting is that "next" field you already had. When a task is not blocked (ready to run) you'd use the "next" field for a linked list of ready to run tasks; when the task is sleeping you might use the "next" field for a linked list of tasks that should wake up at a certain time; when a task is waiting for swap space you might use the "next" field for a linked list of tasks waiting for data from swap space, etc.
Of course later on you might want multiple lists (e.g. one list per task priority, one list per CPU, whatever).
The other thing I'd consider is cache locality (for both cache lines and TLB entries) - cache misses are expensive, and avoiding them helps performance. That task structure is relatively big (compared to cache line size), and different fields are used at different times - e.g. the "next" field and task priority is used when blocking/unblocking tasks and deciding which task to run next, the task's registers and kernel stack are only used during task switches, etc. To improve cache locality you could split the structure into multiple structures (with one structure for each group of things that are accessed at similar times); so that (e.g.) all fields that used when blocking/unblocking tasks and deciding which task to run next (e.g. the "next" field and the task's priority) are packed together into a little structure and all of those little structures (for all tasks) are in the same area (more likely to be sharing the same cache lines and same TLB entries and less likely to cause cache/TLB misses).
Also; it's a bad idea to store a "ticks elapsed since sleep()" because every tick you'd have to update this field for every sleeping task. It's more fun to store a "wake up time" that doesn't need to be updated every tick (and is only set once when a task calls "sleep()" or "nanosleep()" or something). Note that timing is far more complex than people first imagine; partly because it's used for many things (e.g. sleeping and alarms and timeouts) and needs to be "low overhead", and partly because things need to be done in chronological order (and sorted lists are slow when there's many items on the list), and partly because you want very good precision for some things (e.g. "nanosleep()") and don't care much about precision for other things (e.g. networking timeouts), and partly because it's a good idea to support whichever timers the hardware could provide (time stamp counter, local APIC, HPET, PIT, etc) and detect what hardware supports during boot and use the best timer/s that happen to exist. Because it's complex, if you're interested, I'd recommend asking about it (in a different topic) when you're ready to implement it (e.g. possibly after you've got task switching working).
Cheers,
Brendan