System Calls & Basic I/O
How programs ask the kernel to do work — system calls, the errno convention, file descriptors, and open/read/write/close.
This is where we start talking to the kernel directly. A system call is how a user-space
program requests a privileged operation, and file I/O — open, read, write, close — is
the first place we use them. Everything later in the course builds on this.
System calls
Your code runs in user mode, which cannot touch hardware or kernel data directly. When it needs something privileged — open a file, send bytes over a socket — it makes a system call, which switches the CPU into kernel mode, runs the kernel's handler, and returns:
user mode kernel mode
───────── ───────────
your program ──[ syscall ]──▶ kernel does the work ──▶ returns result
◀───────────────────────────────────────In practice you rarely issue the raw instruction; you call thin C library wrappers
(open, read, …) that do it for you. These wrappers follow POSIX, the standard that
lets the same source compile and run across Unix-like systems.
Errors: the -1 / errno convention
Most system calls return -1 on failure and set the global errno to a code
explaining what went wrong (e.g. ENOENT — no such file, EPERM — not permitted). Always
check the return value and report errno properly:
int fd = open("foo.txt", O_RDONLY);
if (fd == -1) {
perror("open"); // prints: open: No such file or directory
// or: fprintf(stderr, "%s\n", strerror(errno));
return errno;
}Files and file descriptors
True to the "everything is a file" idea, an open file is referred to by a file descriptor (fd) — a small non-negative integer. Three are always open:
0— stdin (standard input)1— stdout (standard output)2— stderr (standard error)
A descriptor is an index into a per-process table that points, through a system-wide open-file table (which holds the current offset), to the file's inode:
process fd table open-file table inode table
┌───┬──────┐ ┌───────────────┐ ┌──────────┐
│ 3 │ ───────────────▶ │ offset, flags │ ─────▶ │ inode │ ─▶ data
└───┴──────┘ └───────────────┘ └──────────┘Opening and creating files
open() returns a new descriptor. Its behaviour is controlled by flags (combined with
|) and, when creating, a permission mode:
| Flag | Meaning |
|---|---|
O_RDONLY / O_WRONLY / O_RDWR | read / write / read-write |
O_CREAT | create the file if it doesn't exist |
O_TRUNC | truncate an existing file to zero length |
O_APPEND | always write at the end |
// create-or-truncate for writing, mode 0644 (rw-r--r--)
int fd = open("out.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);(creat(path, mode) is the historical shorthand for open(path, O_WRONLY|O_CREAT|O_TRUNC, mode).)
Reading, writing, closing
read and write move bytes between a buffer and a descriptor, advancing the offset. Both
return the number of bytes actually transferred — which may be less than you asked for,
so real code loops:
char buf[4096];
ssize_t n;
while ((n = read(fd, buf, sizeof buf)) > 0) {
write(STDOUT_FILENO, buf, n); // echo to stdout
}
if (n == -1) perror("read");
close(fd); // always release the descriptorread returning 0 means end of file. Closing frees the descriptor for reuse.
Practice with real code
The Opening a file, Reading a file, Copy a file, and Redirect the output samples are fully worked, commented versions of exactly these calls.
Practice / lab
- Trace real syscalls with
strace lsand findopen,read,write,close. - Write a program that
opens a missing file and reports the error withperror. - Reimplement a tiny
cat: open a file, loopreadinto a buffer,writeto stdout, close.
Homework
References & resources
Required
- The Linux Programming Interface — Ch. 4–5: File I/O
- File descriptor (Wikipedia)
- Handling a file by its descriptor in C
- File descriptors & the file descriptor table (video)
Optional / enrichment
- The Linux syscall table
open(2)·read(2)·write(2)·close(2)- Reading and writing files in C, two ways: fopen vs. open (video)
Self-check
- What distinguishes a system call from an ordinary library function?
- What are the reserved descriptor numbers for stdin, stdout, and stderr?
- How does a program detect and report an error from
open? - Why must
read/writecalls usually run in a loop? - What does
readreturning0mean?