Constructing an functioning kernel requires a large amount of code and development effort. Furthermore, the point at which development feels rewarding is when the kernel is capable of running userspace applications. This makes progressing development challenging, as it takes a long time before the system performs a meaningful task.
The initial development effort is to create a minimal kernel which can run a userspace program. Additionally, core kernel functionality which is hard to retroactively must be put into place. This includes security measures and multi processing support (SMP).
For the minimal case, we can skip pre-emptive scheduling, file system IO, and executable loading by using co-operative scheduling and executing a kernel function directly in ring 3.
The above steps allow ring 3 code to execute, but the code cannot really be useful. However, the program should be able to print to the serial port if the kernel relaxes the restrictions on IO port usage. However, a system call interface needs to be added so support subsequent development, to perform things like memory allocation and yeilding execution.
There are several more components which are needed to have a properly functioning kernel, but these can be carried out independently.
The end result of completing these items would be a kernel capable of running basic programs. Note that some steps can be completed in isolation but will likely need refactoring if done in the incorrect order. For instance, the file system driver can be developed before deciding on a proper VFS API, but it would then need to be changed later. Likewise, an executable loader which accesses the file system driver would need to change as well.
However, this is acceptable because the VFS API is extremely important, and will likely be influcenced by kernel architecture decisions (separate processes per fs as well as asnchronous first API).
This means that individual components created during this time period should be as minimal as possible.
For the initial period of development, the intention to create a microkernel should be ignored. This is simply because separate processes are impossible when the kernel itself does not support processes. Ideally, the kernel will have a sensible user-facing API, which would allow the internal implementation of kernel components to slowly be moved into separate processes.
The number one goal should be to run a userspace program properly. Until acheiving this goal, the kernel code is allowed to be poorly architected and thought-out. All that matter is getting to a point where the code actually does something. At this point, the code should be refactored, which making sure it is still functioning.
Once the kernel is capable of running a userspace program, and has some core solid-ish foundations, the next step is to add the remaining components. This will require refactoring and a lot of combined work between kernel and userspace. For example, the kernel will need to implement filesystem support via a VFS. Userspace will need to support consuming abstractions, the libc must provide POSIX compatibility, and userspace must also implement concrete filesystem drivers. Ideally, userspace programs for creating and inspecting disk images should be added as well. This is just one example, and there are many other components which will need to be implemented.
For constrast, horizontal slices refer the individual components themselves. The idea is that each vertical slice is part of expanding multiple horizontal slices, which effectively means that the whole system becomes more usable. For instance, POSIX compatibility is a horizontal slice, and different system calls fall into different vertical slices.
The file system is the heart of modern operating systems. In many systems, every other API is effectively built on top of the file system. At the same time, the file system API determines the performance and usability of the system. Without a good page caching mechanism, the OS will be painfully slow in comparison to other systems. Unfortunately, there is a lot that needs to be done to create a good VFS API, and exposing this to userspace will require a lot of thinking.
Memory management and the file system are somewhat intertwined, especially considering concepts such as POSIX mmap() and the potential need to swap out memory. Although memory management is a bit less complicated, as it will probably not be mostly done in userspace, it still requires a lot of refactoring. Currently, the kernel can really only immediately map pages, and doesn't support lazy allocation, copy-on-write, or swapping. And privledged kernel-space APIs will need to be added to implement certain drivers in userspace.
Process management is important for POSIX compatibility and the shell. It is especially unclear what abstractions the kernel should expose, because the goal is not implement POSIX directly, but to have userspace be able to effectively emulate it. Of particular note is the need to implement fork() and POSIX signals.
Time keeping is comparatively simple, but there are additional drivers the kernel needs to implement (RTC, HPET, etc). Userspace must expose this API, which involves working on the chrono library component, and POSIX compatibility. And some timer mechanism needs to be implemented.
Dynamic linking requires some work in the kernel, to support ELF interpreters. However, the bulk of the work is in the dynamic linker, but libc also needs to be updated to support it.
Networking is actually one of most execiting components to implement. The kernel will need to implement a network stack, and userspace needs to be able to consume it. With the focus asynchronous IO, the hope is to be able to implement a performant system. It would be exciting to compare this implementation on between Linux and Iros.
Graphics is likely to be very difficult. Most likely, the goal should be to port X11 or Wayland to the system. This will involves lot of POSIX compatibility work, and a choice must be made as to whether to support the Linux DRM API or to port these programs to some other custom API.
Audio is a lot approachable than graphics, but I don't know much about it. The kernel will need to have some sort of API and support audio drivers, and userspace will need to implement a audio server. Hypothetically, porting some Linux solution could also be done.
Synchonization primitives are important for applications and the pthreads library. The kernel will need to implement either futex
or some other mechanism for implementing these sorts of things.
Containerization is a bit of a stretch goal, but it would be nice to have. The kernel will need to implement some sort of virtualization mechanism, and userspace will need to implement a container runtime. Look at Linux KVM for inspiration.