~/learn

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:

  • 0stdin (standard input)
  • 1stdout (standard output)
  • 2stderr (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:

FlagMeaning
O_RDONLY / O_WRONLY / O_RDWRread / write / read-write
O_CREATcreate the file if it doesn't exist
O_TRUNCtruncate an existing file to zero length
O_APPENDalways 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 descriptor

read 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 ls and find open, read, write, close.
  • Write a program that opens a missing file and reports the error with perror.
  • Reimplement a tiny cat: open a file, loop read into a buffer, write to stdout, close.

Homework

References & resources

Required

Optional / enrichment

Self-check

  1. What distinguishes a system call from an ordinary library function?
  2. What are the reserved descriptor numbers for stdin, stdout, and stderr?
  3. How does a program detect and report an error from open?
  4. Why must read/write calls usually run in a loop?
  5. What does read returning 0 mean?

On this page