I remember wondering if you could make a bootloader that jumps directly into your program’s main function, and then run without any operating system to minimize overhead.
The answer is probably, but it would be a real pain. Without an OS you’d be responsible for handling multithread/core scheduling, managing memory, all the device hardware controllers (e.g. drives, network cards, keyboards), any abstractions like file systems, networking and a bunch of small stuff like error handling for processor interrupts.
Turns out these are all fundamentals of operating systems: you’d just be building your own OS to package with the program you want to boot into!
What is an Operating System
An operating system is a software controlling the operation of a computer system and its resources - wiki.osdev.org
An operating system is the supervising software that sits between hardware and all other programs, trying to provide safe and convenient ways of interacting with resources. The operating system facilities many programs running on the system by managing and virtualizing resources (e.g. virtualizing CPU so multiple programs run on a single core) and providing isolation (e.g. memory) while maintaining minimum performance overhead, and reliability.
Kernel vs Userland
Typically operating systems provide two pieces:
- kernel: the privileged system that does all of the above to make programs run
- userland programs: utilities you need or expect, such as a shell, ls, libraries for system calls, package manager, maybe a desktop environment
For example, many distributions like Ubuntu, NixOS, Arch all use the Linux kernel, even though the userland programs and thus user experience is very different. Other kernels include: FreeBSD, Windows NT.
What does a kernel provide?
The major things you might expect in a “reasonable” operating system kernel are:
- Process management
- Creating/managing/deleting processes (a running program/executable) and scheduling which gets to run on a processor at a given time.
- Memory Management
- Managing areas of physical memory for the kernel and processes, providing isolation between processes
- Virtual memory: giving processes a virtual address space (the illusion of all the memory) and mapping it to some amount of physical memory
- Hardware Device Drivers
- Physical devices have their own controller (e.g. keyboard, mouse), a device driver is the software managing this hardware controller and abstracts handling the device
- I thought you’d see GPU drivers here for example, but those are generally added as kernel modules. Loadable kernel modules are a way of running extra code in the kernel but not including it in the core distribution
- Files and directories for interaction with persistent I/O
Those are the core 4, but also some other things like:
- Stuff like a TCP stack, interacting with the network card
- Interprocess Communication (IPC)
- e.g. signals, pipes, RPC, message passing
- Event/Interrupt handlers for hardware events
- Security and access policies
Kernels also differ in their architecture, especially around what they keep in kernel space vs userspace (e.g. the filesystem or driver).
Monolithic kernels (e.g. Linux) keep all or most of the services in kernel space, making them generally fast. Hybrid kernels (e.g. Windows NT and above) put some components in userland, especially 3rd-party features like drivers which may be buggy/malicious. Micro kernels try to run services like filesystems and drivers in userspace, relying on messaging between services.
Unix Architecture Diagrams (Update 07-04-17)
Diomidis Spinellis made some awesome architecture diagrams of two Unix operating systems. I like it because it’s a really comprehensive (and organized) way of seeing everything thats provided in user space and kernel space.