Contiki
Last but not least (except in terms of size) is Contiki. While MenuetOS feels like it was written to show that it could be done, I still find it hard to believe that Contiki is possible, even after seeing it. If you thought 32MB of RAM and 1.44MB of disk space was a fairly low set of system requirements for a multitasking graphical operating system, then you are unlikely to believe the system requirements for Contiki—2KB of RAM and 40KB of ROM.
Originally built for embedded systems, particularly sensor networks, the operating system has been ported to a large number of systems including the Commodore 64. Even on the C64, it supports a full TCP/IP stack and can run a web browser and server. If you don’t have graphical capabilities locally, there’s even a VNC server included for remote access.
The kernel is event-driven, and permits individual applications to support pre-emptive multithreading. The core system is built around the idea of protothreads, similar in concept to coroutines. Unlike true threads, a protothread does not have a local stack. While a protothread is running, it may use the stack, but as soon as it enters a blocking state it will lose the contents of the stack frame. Local variables do not persist between blocking operations in protothreads.
Because local variables are not preserved, most coroutines have to rely on statically allocated global variables. In practice, this is not a huge problem for Contiki; target systems are sufficiently memory-constrained that it is quite rare for them to be able to implement luxuries such as a heap and dynamic memory allocation. The TCP/IP stack, uIP, has a number of limitations defined at compile time, such as the number of fragmented IP packets that can be cached for reassembly and the number of TCP connections permitted. This allows the maximum memory usage to be determined at compile time, which eliminates a lot of potential out-of-memory conditions from the system. Of course, this comes at a cost. Rather than run out of memory, you can now run out of connections (for example). In general, however, it is possible to ensure that these limits are larger than an embedded system will encounter.