This is a tad long winded so if you're not in the mood then please move on before complaining (no one likes complaints simply because the person didn't bother to read).
First, basic overview of design goals:
- Microkernel with (minimal) drivers in ring1/2 and their segments appropriately assigned (I'm planning to still use sysenter/syscall stuff and I have some questions about that)
- Minimal hardware support (specific hardware supported... don't have it, out of luck).
- Single-process, multi-task. Multi-core/cpu necessary, but 4+ core/cpu highly recommended.
- General IRQs firing on CPU0, drivers running on CPU0, any general task/gc on the ring0 level, and the main process on CPU0.
- Main process "workers" are sticky to CPU1..n and there's no preemption except during panic.
- All I/O is async by nature unless explicitly overridden.
- 64-megs minimum RAM (including a simple ring3 process)
- The heart of the kernel is something similar to kqueue or epoll or IO completion ports, but much more powerful and much more elegant.
- A basic TTY and user system to allow controlling/reviewing the system, but nothing beyond that.
- Support most environments (I already said this was a non-goal... repeating).
- POSIX/BSD/LIBC/whatever compatibility of any kind. The antithesis of a goal.
- Support of any standards or designs that might hinder the intention of this design (such as would cause crippling resource consumption on CPU0)
- Conventional ring3 task switching (although semi-support for CPU0 given it's need to handle the drivers).
- Support for things like OpenGL, any kind of window management system, stdio, what-have-you... it's all out the window. No web browsers or super-duper shell scripting or wine or whatever. You get the picture.
Full disclosure: I was inspired by this idea through a cascade of events and thoughts, and the one that solidified it for me was the random OS I came across. [snip] (EDIT: Brendan pointed out that I was thinking of Barmetal OS. check it out if you haven't yet)
The kernel's responsibilities will include:
- Management of all CPU0 IRQs
- Task switching between the kernel, all drivers, and the main process thread.
- Handling low-level errors and fataling gracefully as necessary.
- Proxying/buffering/etc all "driver" requests (filesystem/network/etc).
- Managing all I/O wait states in a poll() state (the heart of this)
- Avoiding event starvation via defined distribution patterns and event preemption/aborting as necessary.
- Provision of generic event tracking and firing.
- Provision of low to mid-level atomic, semaphore, mutex, and consumer/producer models.
- Panic support and recovery.
Code: Select all
handle sockworker = undefined;
handle logger = undefined;
handle userland_event = undefined;
handle conn_consumer = undefined;
struct worker {
// worker specific vars can go in here (really just thread-local-storage)
handle events;
};
main() {
// In CPU0
handle events = events_create();
handler logger = open(events, "file:///var/log/whatever.log", APPEND);
// WAITFOR here does a couple of things... tells the write call to I/O block,
// and possibly rw locks (in this case write). The workers aren't started
// yet so contention isn't possible... so the rwlock doesn't matter.
write(logger, "Starting up\n", WAITFOR);
sockworker = worker_register(sockworker_setup, sockworker_teardown, sizeof(worker));
conn_consumer = worker_consumer(sockworker, sockworker_acceptconn, ROUNDROBIN, nullptr);
worker_startup(sockworker);
handle listener = listen(events, "tcp://[*]:12345");
events_on(events, PROCESS | EXCEPTION, UNHANDLED, unhandled_error, nullptr); // << just an idea... don't know where it goes yet though.
events_on(events, TIMER | ONESHOT, 60*60*1000000, after_one_hour, nullptr);
events_on(events, LISTENER | INCOMING, listener, do_accept, nullptr);
userland_event = events_register(events);
events_on(events, USERLAND, userland_event, arbitrary_userland_event, nullptr);
status = events_wait(events, INFINITE);
// We're shutting down for 'status' reason now... a couple of things:
// First, if it's a graceful shutdown (SIGTERM or whatever), then
// sockworker_teardown() has already fired and it _had_ an opportuinity to
// gracefully shutdown (or hang indefinitely if it's a jerk).
// An event hook like PROCESS | HANG could probably be be provided to force
// a SIGKILL of hanging workers after x-amount of time.
};
do_accept(handle events, handle listener, void* userland /* is nullptr */) {
// We're still in the "main" process on CPU0.
// This _could_ be given to a worker, but it's just an accept(), so why?
handle conn = accept(listener); //immediate so no blocking/event poll is needed
worker_give(conn_consumer, conn); //this, however... _may_ be blocking depending on system resource limits
};
sockworker_setup(worker* wls) {
// This is now run on CPU1..n in parallel
// Initialize anything in wls (worker* ... worker local storage)
wls->events = events_create();
// Note the possible syncronization issue below... this could be handled
// kernel-level because of the WAITFOR status (a classic rwlock).
write(logger, "Worker online\n", WAITFOR);
};
sockworker_teardown(worker* wls) {
// Still in CPU1..n
// Shutdown/waitfor any active connections this worker was handling
write(logger, "Worker going offline\n", WAITFOR);
// Destroy anything in wls
};
sockworker_acceptconn(worker* wls, handle conn, void* userland /* is nullptr */) {
// Still in CPU1..n
// Possible contention again, but this time we don't care. Just try to gain
// a lock... if not, move on without completing the action.
write(logger, "Hey there, I've got a connection to work with!\n", FIREANDFORGET);
// Normally, you would place the conn in a poll and track it's state of course.
// For example, a simple HTTP server would have a structure for that handle
// and figure out to do on each event based on what state it's in.
// Of course, additionally, we'd register/handle all appropriate events like
// READYREADY, EXCEPTION, etc.
// Here though we'll just do a simple send-and-hup.
events_on(wls->events, CONNECTION | WRITEREADY, conn, socketworker_can_write, wls);
events_on(wls->events, CONNECTION | HUP, conn, socketworker_conn_hup, wls);
};
socketworker_can_write(handle events, handle conn, worker* wls /* this was "userland" elsewhere */) {
// Still in CPU1..n
// What's interresting here is that WAITFOR _usually_ doesn't actually block
// this time because this function is _only_ fired when the handle is writable.
// We can extend this so it's always non-blocking of course (even when the buffer is full)
// by simply providing a WRITESIZEREADY event with an exact size for what we want to
// write.
write(handle, conn, "Hello there!\nAnd goodbye...", WAITFOR);
close(handle, HARD | FIREANDFORGET); // immediate if possible, queued if necessary, killed if sys queue limits are reached.
};
socketworker_conn_hup(handle events, handle conn, worker* wls) {
// Still in CPU1..n
// Note that the below isn't worker-local
events_trigger(events, USERLAND, userland_event, "whatever userland param\n" /* << this could be a struct ref or handle or whatever */);
};
arbitrary_userland_event(handle events, handle event, void* inital_bound_arg, char* triggered_arg) {
// Haven't decided if we're in CPU1..n, or CPU0 (the registrar of the event) here... probably will be flag-controlled.
// Whatever, now a user-defined event has been fired and we can do whatever with the args.
// Just an example.
write(logger, triggered_arg, FIREANDFORGET);
};
after_one_hour(handle events) {
// Yes, yes, I know, the args aren't right, but it's a prototype example. Simply a unhindered chain-of-thought.
write(logger, "It's been an hour and I need to go to sleep... good night.\n", WAITFOR);
process_term();
};
Oh yeah... googling turned up nothing... anyone know if I'm stepping on anyone's toes by using OdinOS?
Thanks a great deal in advance for the thoughts.
EDIT: As a name... I meant using "OdinOS" as a name (not as some kind of product that I'm "using")... I quite like it for a bunch of synchronistic reasons and would love to hang onto it if no one's using it.
EDIT: Removed the reference to SMP as OSwhatever correctly pointed out that it was antithetical.
EDIT: Clarification that I don't plan to support anything that would hinder the overall design goal of this OS. Anything. As per clarification inspired by Kevin.