Skip to content

Expert · Embedded C, system calls and careers · topic 58 of 60 · 30 min

C on Linux: files and processes with system calls

Explanation

On Linux, programs ask the kernel for services through system calls. File I/O at this level uses integer file descriptors (0 = stdin, 1 = stdout, 2 = stderr) with open, read, write and close; printf and fopen are library layers built on top of them. Failures return -1 and set errno (print it with perror). fork creates a new process, exec replaces its program, and wait collects its exit status.

User space and the kernel

Your program runs in user space and cannot touch hardware, other processes' memory or the disk directly. To do those things it asks the operating-system kernel through a system call. The C library wraps each system call in a function: open, read, write, close, fork, execvp, waitpid and so on (documented in section 2 of the manual: man 2 open). printf, fopen and fgets (section 3) are library functions that add buffering and formatting and eventually call write or read.

File descriptors

c
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main(void) {
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);   /* returns a small int, or -1 */
    if (fd == -1) { perror("open log.txt"); return 1; }
    const char msg[] = "order 1042 placed\n";
    ssize_t n = write(fd, msg, sizeof msg - 1);                       /* bytes written, or -1 */
    if (n == -1) perror("write");
    close(fd);

    write(1, "done\n", 5);        /* fd 1 is stdout */
    return 0;
}
fdStreamDefault
0stdinkeyboard / piped input
1stdoutterminal / redirected output
2stderrterminal (error messages)

read(fd, buf, n) may return FEWER bytes than requested (a "short read"), for example from a pipe or network socket, and 0 at end of file. Robust code loops until it has what it needs. The same is true for write on sockets and pipes.

Errors: -1 and errno

System calls report failure by returning -1 and setting the global errno to a code such as ENOENT (no such file) or EACCES (permission denied). perror("context") or strerror(errno) turns it into a readable message. Check errno only right after a call has failed, because other calls may change it.

Processes: fork, exec, wait

c
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>

int main(void) {
    pid_t pid = fork();                        /* now TWO processes run from here */
    if (pid == -1) { perror("fork"); return 1; }
    if (pid == 0) {                            /* child */
        char *args[] = {"ls", "-l", NULL};
        execvp("ls", args);                    /* replace the child's program with ls */
        perror("execvp");                      /* only reached if exec failed */
        _exit(127);
    }
    int status;                                /* parent */
    waitpid(pid, &status, 0);                  /* wait for the child to finish */
    if (WIFEXITED(status)) printf("child exited with %d\n", WEXITSTATUS(status));
    return 0;
}
This is how a shell runs commands: fork, exec in the child, wait in the parent.
Careful: Every system call can fail; always check the return value. Forgetting to wait for children leaves "zombie" processes; forgetting to close file descriptors leaks them until the process hits its limit ("Too many open files"). And fork in a program with threads or open stdio buffers has subtle traps (buffered output can be printed twice): fflush(stdout) before fork.

Where to go next

The same system-call style continues into sockets (network programming), pipes and signals, memory mapping (mmap) and threads (pthread_create). These topics are the core of Linux systems programming and of many interviews for systems roles; the man pages and the book "The Linux Programming Interface" are standard references.

Think of it like this: System calls are like the counter at a government office: you cannot walk into the records room yourself; you fill a form (call open with arguments), the clerk (the kernel) does the work and gives you a token number (the file descriptor) to use for every later request. If the request is refused, you get a rejection slip with a code (errno).
Tip: Run strace ./prog on Linux to see every system call your program makes. It is an eye-opener: a simple printf turns into a write(1, …) call.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

C on Linux: files and processes with system calls · 1/6

System calls

In this lesson you will talk to the Linux kernel with system calls.

Prefer a video by a teacher? Find free videos on “linux system calls c open read write fork exec wait tutorial” on YouTube ↗

In real life: A tiny shell

Writing a minimal shell is a classic systems project: read a line, split it into words, fork, exec the command in the child, and wait in the parent. Many Indian university operating-systems labs and systems-programming interviews ask for exactly this.

c
char line[256];
for (;;) {
    printf("mysh> ");
    fflush(stdout);
    if (!fgets(line, sizeof line, stdin)) break;
    char *argv[16]; int argc = 0;
    for (char *tok = strtok(line, " \t\n"); tok && argc < 15; tok = strtok(NULL, " \t\n")) argv[argc++] = tok;
    argv[argc] = NULL;
    if (argc == 0) continue;
    if (strcmp(argv[0], "exit") == 0) break;
    pid_t pid = fork();
    if (pid == 0) { execvp(argv[0], argv); perror(argv[0]); _exit(127); }
    if (pid > 0) waitpid(pid, NULL, 0);
}

Practice (0/3 done)

Task 1 · Question

What does write(2, "error\n", 6) do?

Task 2 · Question

After pid = fork() succeeds, what does each process see?

Task 3 · Practice

On Linux (or WSL), write a C program that copies one file to another using only open, read, write and close (no stdio), in 4 KB chunks, handling short writes and reporting errors with perror. Then write the tiny shell from the example, run a few commands, and use strace on your copy program.

Quick quiz

Question 1

1.Which man page section documents system calls?

Question 2

2.What does read return at end of file?

Question 3

3.What does exec do?

Question 4

4.What is errno?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Which man page section documents system calls?

Answer: 2

Section 2: system calls; section 3: library functions.

What does read return at end of file?

Answer: 0

0 means no more data; -1 means an error.

What does exec do?

Answer: Replaces the current process's program with another

fork creates; exec replaces.

What is errno?

Answer: A code describing the last error from a failed call

Read it right after the failure.

What does fopen return when the file cannot be opened?Related

Answer: NULL

Check and use perror.

From Reading and writing text files

What does strerror(errno) return?Related

Answer: A text description of the last error

For example "Permission denied".

From assert, logging and testing C code

What exit code means a command succeeded?Related

Answer: 0

0 is success. Any non-zero code is a failure; 127 means command not found.

From Processes, signals and systemd services

What does the Linux kernel do?Related

Answer: Talks to the hardware and runs processes

The kernel is the core of the OS: hardware, memory, processes.

From What Linux and Red Hat Enterprise Linux are

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Embedded C, system calls and careers” before its exam: C interview questions, Projects and careers with C.
  2. Module 12 exam: Embedded C, system calls and careers · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. 60 topics left in the course before the final exam.

← Previous: Embedded patterns: interrupts, timing and constraints