Learning Question
What happens when a program reads input or writes output?
Input and output are not direct conversations between source code and a physical device.
A program usually performs I/O through file descriptors, library buffers, system calls, kernel buffers, device drivers, and hardware or kernel-managed endpoints.
The first mental model is:
I/O is a chain from program-level calls to OS-managed resources, and the resource type determines what reading or writing really means.
Program Calls and Library Buffers
Many programs use library-level I/O functions.
Examples include printf, scanf, fgets, and C++ streams.
These calls may use user-space buffering before they make system calls.
For example, printf("hello") may place bytes in a user-space buffer instead of immediately sending them to the kernel.
The actual write system call may happen when the buffer fills, when a newline is printed to a terminal, when the stream is flushed, or when the program exits normally.
This distinction explains why output sometimes appears later than expected.
The program wrote to a library buffer.
The OS resource did not necessarily receive the bytes yet.
System Calls and Kernel Buffers
At the system call level, I/O commonly uses operations such as read and write.
The process passes a file descriptor, a user-space memory buffer, and a byte count.
The kernel validates the request and transfers data between the process buffer and the OS-managed resource.
Depending on the resource, the kernel may use its own buffers.
For example:
- disk I/O may interact with page cache and file-system buffers
- terminal input may wait for line discipline behavior
- pipe data may live in a kernel pipe buffer
- socket data may move through network buffers
The same read call shape can hide different internal mechanisms.
Blocking
I/O may block.
If a program reads from a terminal and no input is available, the thread may sleep.
If it reads from a pipe with no data while the writer is still open, it may wait.
If it writes to a full pipe or socket buffer, it may wait for space.
Blocking is not a busy loop in ordinary kernel-managed I/O.
The kernel can mark the thread as waiting and let other runnable work use the CPU.
This is one of the main ways I/O connects to scheduling.
End of File and Completion
Input does not always fail just because no more bytes are returned.
For regular files, a zero-byte read at the end usually means end of file.
For pipes and sockets, zero bytes may mean the other side closed its writing end or the connection ended cleanly.
For terminals, behavior depends on terminal settings and input events.
The meaning of “done” depends on the resource.
This is why I/O code must interpret return values, not just assume that every read either returns requested bytes or crashes.
Devices and Drivers
Physical devices are usually accessed through drivers.
Application code does not normally control disk controllers, network cards, keyboards, or displays directly.
The kernel and drivers mediate access.
The program may see a descriptor or a file-like interface.
The lower layers translate requests into device-specific operations.
This separation is part of protection and portability:
Programs use OS abstractions. Drivers and the kernel handle device-specific details.
Standard Streams
Standard input, output, and error are ordinary examples of descriptor-based I/O.
A program can read from descriptor 0 and write to descriptors 1 and 2.
The program does not need to know whether those descriptors point to a terminal, file, pipe, or another resource.
This is why redirection works.
A shell can connect a program’s standard output to a file or pipe before the program starts.
The program still writes to standard output.
The descriptor table determines where the bytes go.
Core Mental Model
I/O crosses multiple layers.
The source code calls a library or system function.
The process uses descriptors and memory buffers.
The kernel manages resources, permissions, buffers, waiting, and device-specific behavior.
When diagnosing I/O behavior, ask:
Which layer has the data now: user-space buffer, kernel buffer, file system, pipe, socket, terminal, or device?
Final Summary
Input and output are OS-mediated interactions with open resources.
A program may call simple functions, but the actual behavior depends on buffering, descriptors, system calls, resource type, blocking, kernel state, and device or endpoint semantics.