09. Operating Systems
Operating Systems#
At the lowest level, a computer is a collection of processors, memory and devices. Those components do not inherently provide files, processes, windows, users or even a safe way for two programs to run at once. An operating system creates those higher-level concepts and manages access to the underlying hardware.
The operating system therefore sits between user programs and the machine:
flowchart TB
U["Users"] --> A["Applications"]
A --> L["Libraries"]
L --> K["Operating-system kernel"]
K --> CPU["CPU"]
K --> MEM["Memory"]
K --> DEV["Storage, network and other devices"]
This is related to the abstraction layers described in Preamble. A C program can request “write these bytes to this file” without knowing the voltage levels of the storage device, the structure of its controller or whether the data ultimately lands on an SSD, hard disk or network filesystem.
A World Without an Operating System#
Without an operating system, every program would need to manage the physical machine itself. Among other things, a program would need to:
- initialise the computer and decide how to load code;
- understand the exact model and protocol of every connected device;
- decide where its data may live in physical memory;
- invent a method for locating persistent data on raw storage;
- coordinate the CPU with every other program;
- prevent other code from reading or overwriting its state;
- implement input, output and networking directly against hardware.
A program written for one hardware configuration would be deeply tied to it. Changing the storage controller, network device or even memory layout could require a rewrite.
The operating system centralises this difficult machinery. Programs target a stable interface supplied by the OS, whilst the OS and its device drivers deal with the hardware-specific details.
What an Operating System Provides#
Abstraction#
The OS presents simpler virtual objects in place of raw hardware:
| Hardware reality | OS abstraction |
|---|---|
| physical CPU cores and timers | processes and threads |
| physical RAM chips | virtual address spaces |
| storage blocks | files and directories |
| network controller buffers | sockets |
| keyboard/display devices | terminal input and output streams |
These abstractions are not merely convenient names. They impose useful behaviour and rules. A file has a pathname, permissions and a current offset when opened; none of those ideas exist in a raw sequence of disk sectors.
Resource management#
Many programs may want the CPU, memory or a device simultaneously. The OS decides how these resources are shared:
- the scheduler decides which process runs on a CPU;
- the virtual-memory system gives each process its own address space;
- filesystems coordinate persistent storage;
- drivers coordinate device access;
- permissions determine which operations a user or process may perform.
Protection#
One process should not be able to read another process's passwords or overwrite the kernel merely because it calculated a bad pointer. The operating system works with hardware protection mechanisms to isolate programs and enforce permissions.
Portability#
Two computers can contain different hardware whilst presenting the same operating-system interface. A program using the POSIX read interface does not need a separate implementation for every model of storage device.
This does not make every compiled program universally portable. The executable must still target the CPU architecture, executable format and operating-system ABI described in Running MIPS. The OS interface nevertheless removes a large amount of hardware-specific work from ordinary applications.
Kernel Mode and User Mode#
Protection depends on the CPU providing at least two privilege levels.
Kernel mode#
The operating-system kernel runs in privileged mode. It can:
- configure memory protection;
- access device-control registers;
- execute privileged CPU instructions;
- respond to interrupts;
- inspect or modify the state of user processes;
- schedule which program runs next.
Because kernel code has authority over the whole machine, a kernel bug can crash or compromise the entire system.
User mode#
Ordinary applications run in a non-privileged mode. They cannot directly access arbitrary physical memory or devices. Their instructions are constrained by protection state configured by the kernel.
If user code could simply switch itself into kernel mode, the separation would be useless. Instead, the CPU provides carefully controlled entry points through exceptions, interrupts and system-call instructions.
flowchart TB
P["User program<br/>number + arguments"] --> C["CPU performs controlled<br/>entry into kernel mode"]
C --> K["Kernel validates request<br/>and permissions"]
K --> H["Permitted hardware<br/>or resource operation"]
H --> R["Kernel prepares<br/>result or error"]
R --> U["Program resumes<br/>in user mode"]
System Calls#
A system call is a request from a user program for the kernel to perform an operation which the program cannot or should not perform directly.
Typical system calls include:
- reading and writing bytes;
- opening or closing a file;
- creating, replacing or terminating a process;
- allocating or mapping memory;
- communicating over a network;
- obtaining time or filesystem metadata.
A system call is not an ordinary function call, even though a C wrapper may make it look like one. The request crosses a privilege boundary, changes the CPU into kernel mode at a controlled address, validates untrusted arguments and eventually returns to user mode.
Important
- System call is not the same as context switch
A system call switches privilege mode so the kernel can serve the current process. The scheduler may still return to that same process. A context switch specifically saves one execution context and restores another, as described in Processes.
Why validation matters#
The kernel must assume that every user-space argument is untrusted. Before writing to a file, for example, it may need to check:
- whether the supplied file descriptor exists;
- whether it was opened for writing;
- whether the source buffer belongs to the calling process;
- whether the requested byte count is valid;
- whether the device or filesystem can accept the data;
- whether a signal or resource limit interrupts the operation.
A syntactically valid system call can therefore fail. The kernel does not grant a request merely because the caller reached the correct entry point.
Note
Checkpoint
- User programs run with restricted privilege; the kernel controls protected hardware and shared resources.
- A system call crosses that protection boundary through a defined interface.
- Crossing into the kernel is not the same thing as switching from one process to another.
Three Layers of I/O in C#
On Linux, an operation such as printing can be requested at several layers.
Calling syscall directly#
The C library exposes a generic syscall function which takes a system-call number and its arguments:
#include <sys/syscall.h>
#include <unistd.h>
char message[] = "Hello!\n";
syscall(SYS_write, 1, message, 7);This is useful for experimenting or accessing a newly added call without a wrapper. It is otherwise difficult to read, easy to misuse and tied to a particular operating-system interface and architecture.
Using a named C library wrapper#
The C library on a Unix-like system provides POSIX functions whose names and types correspond to system calls. These interfaces are supplied by libc, but functions such as write() are not part of the ISO C standard library:
#include <unistd.h>
char message[] = "Hello!\n";
write(STDOUT_FILENO, message, 7);The wrapper supplies a readable name, performs the architecture-specific transition and translates kernel error conventions into the C errno interface.
Using a higher-level library#
The <stdio.h> library provides buffered and formatted I/O:
#include <stdio.h>
printf("Hello!\n");printf eventually causes bytes to be written, but it also parses a format string, converts values to text and normally buffers output. One library call does not necessarily correspond to one system call.
flowchart TB
APP["Application"] --> STDIO["stdio<br/>printf, fopen, fread"]
STDIO --> WRAP["libc wrappers<br/>write, open, read"]
WRAP --> SYSCALL["system-call boundary"]
SYSCALL --> KERNEL["kernel"]
Higher layers are generally more portable and convenient. Lower layers provide greater control and expose the mechanics which COMP1521 is trying to teach. File Systems concentrates on the named open, read, write and close wrappers.
Mipsy System Calls#
Mipsy emulates a MIPS processor and also supplies a tiny teaching environment for system services. A MIPS program places a service number in $v0, places arguments in designated registers and executes syscall:
li $v0, 4 # print_string service
la $a0, message # address of argument string
syscallThis resembles the structure of a real system call, but the service numbers and behaviours are specific to the mipsy/SPIM-style teaching interface. They are not Linux x86-64 system-call numbers and should not be mixed with them.
| Environment | Service selector | Arguments | Transition mechanism |
|---|---|---|---|
| mipsy teaching system | $v0 |
usually $a0 onwards |
MIPS syscall instruction |
| Linux through libc | wrapper name such as write |
C function arguments | wrapper performs architecture-specific transition |
| direct Linux call | architecture-specific number | architecture ABI | architecture-specific syscall instruction |
The syscall table in Using MIPS is therefore a table for mipsy programs, not a universal MIPS or Linux table.
The ABI Boundary#
An operating-system ABI specifies details such as:
- system-call numbers;
- registers or stack locations used for arguments;
- how errors and return values are represented;
- data structure layouts;
- executable and object-file conventions;
- rules for calling functions across compiled modules.
This is why source code can be recompiled for different systems but an already compiled executable generally cannot simply move between them. Both the processor's instruction set and the OS ABI must match.
Inspecting System Calls#
On Linux, strace records the system calls made by a process:
strace ./programTo focus on selected calls:
strace -e trace=openat,read,write,close ./programThe output reveals operations performed indirectly by libraries and by program start-up. Even a tiny C program usually makes more system calls than the lines you wrote suggest, because the dynamic loader and runtime must prepare the process.
Reading the Manual#
The Linux manual is divided into numbered sections. The most relevant sections for COMP1521 are:
| Section | Contains | Example |
|---|---|---|
| 1 | executable commands | man 1 ls |
| 2 | system calls | man 2 read |
| 3 | library functions | man 3 printf |
| 7 | conventions and overview pages | man 7 man-pages |
When names overlap, the section removes ambiguity. man 2 open documents the system-call interface, whilst another section may document a command or unrelated library interface.
Useful habits include:
man 2 open
man 2 read
man 3 perror
man 7 errnoThe manual gives the required headers, function signature, return convention, errors and relevant standards. It will be available in the exam, so learning how to locate information is more valuable than memorising every error code.
Note
Checkpoint
- Application code usually calls a library wrapper rather than issuing a raw syscall instruction.
- The ABI defines where arguments, results, and syscall numbers travel across the boundary.
- Manuals are part of the interface: check parameters, return values, errors, and required headers together.
Common Mistakes#
- Treating the OS as merely a graphical desktop. The kernel's central roles are abstraction, resource management and protection.
- Assuming a library function is itself a system call. A library may perform several calls, delay them through buffering or perform none at all.
- Mixing mipsy service numbers with Linux system-call numbers.
- Assuming entering the kernel means a request succeeds. Arguments and permissions are checked and operations can fail.
- Believing user mode can access only “user-friendly” instructions. Most ordinary arithmetic and control instructions remain available; privileged operations are restricted.
- Treating Linux system-call numbers as stable across CPU architectures.
- Forgetting that a compiled program depends on both its CPU instruction set and operating-system ABI.
Practice#
- Why can an application not safely be allowed to write directly to an arbitrary disk sector?
- What is the distinction between kernel mode and user mode?
- Trace the conceptual path from
printf("hello")to bytes appearing in a terminal. - Why is a libc wrapper such as
writeeasier to use than genericsyscall? - Why might one call to
printfproduce no immediatewritesystem call?
Note
- Answers
- It could corrupt other programs' files or filesystem metadata; the kernel must enforce ownership, structure and coordination.
- Kernel mode can execute privileged operations and manage the whole machine, whilst user mode is constrained to the process's permitted resources.
printfformats and buffers text in libc; libc eventually invokes awrite-style wrapper; the CPU enters the kernel; the kernel validates the descriptor and sends bytes to the terminal device.- It has a meaningful name and typed parameters and hides architecture-specific syscall-number and transition details.
- Standard I/O is buffered, so libc may retain the bytes until the buffer fills or the stream is explicitly flushed or closed. A newline also triggers a flush when the stream is line buffered, as terminal output commonly is.
The next note follows one of the most important OS abstractions from beginning to end: files and file descriptors. The same abstraction and protection machinery later lets the kernel create and coordinate Processes.