UNIX/Linux System Calls
Interact with the operating system directly! Learn open, read, write, fork, exec, and other system calls that give C its power on UNIX/Linux.
Track Your Progress
Sign in to save your learning progress
What You Will Learn
- ✓Understand system calls vs library functions
- ✓Use file descriptors (open, read, write, close)
- ✓Create processes with fork and exec
- ✓Handle errors with errno and perror
- ✓Work with directories (getcwd, chdir, mkdir)
?Why Learn System Calls?
Standard library functions like printf() are convenient, but they're builton top of system calls. Understanding system calls gives you:
Direct Control
No buffering, no overhead
Systems Programming
Build OS tools, servers
Linux/UNIX Mastery
Understand how OS works
Career paths: DevOps engineers, kernel developers, embedded programmers, and security researchers all need deep system call knowledge.
01What are System Calls?
C and the Operating System
System calls are how your C programs talk to the operating system (Linux/UNIX). They let you create files, run programs, manage memory, and more — directly through the OS kernel!
How System Calls Work
User space
Wrapper function
── Kernel Boundary ──
Kernel space (privileged)
Disk, Network, etc.
System calls cross the user-kernel boundary with special CPU instructions
Library Functions (stdio.h)
High-level, buffered, portable
System Calls (unistd.h)
Low-level, direct, OS-specific
Common System Calls
| Category | System Calls | Header |
|---|---|---|
| File I/O | open, close, read, write, lseek | <unistd.h> <fcntl.h> |
| Process | fork, exec, wait, exit, getpid | <unistd.h> <sys/wait.h> |
| Directory | mkdir, rmdir, chdir, getcwd | <unistd.h> <sys/stat.h> |
| File Info | stat, chmod, chown, unlink | <sys/stat.h> |
02File Descriptors
• File Descriptors — Your Ticket to Files
Think of a file descriptor like a coat check ticket at a restaurant:
You give your coat → Get ticket #42
open() → returns fd (like 3)
• Show ticket #42 → Get coat back
read(fd) → access the file
Process File Descriptor Table
When you call open("data.txt"), the kernel finds the next available fd (3) and returns it
fd vs FILE* — What's the Difference?
fd (int) — Raw OS-level handle, no buffering, direct control
FILE* — C library wrapper, buffered, portable, built on fd
Under the hood: fopen() calls open() and wraps the fd!
03open() - Open a File
open() - Open or Create a File
int open(const char *pathname, int flags, mode_t mode);
Header: <fcntl.h>
Common Flags
| Flag | Description |
|---|---|
| O_RDONLY | Read only |
| O_WRONLY | Write only |
| O_RDWR | Read and write |
| O_CREAT | Create if doesn't exist |
| O_TRUNC | Truncate to zero length |
| O_APPEND | Append to end |
1#include <stdio.h>2#include <fcntl.h> // For open() and flags3#include <unistd.h> // For close()45int main() {6 // Open existing file for reading7 int fd1 = open("data.txt", O_RDONLY);8 if (fd1 == -1) {9 perror("Error opening file");10 return 1;11 }12 printf("File opened, fd = %d\n", fd1);13 close(fd1);14 15 // Create new file (or truncate existing)16 // 0644 = rw-r--r-- (owner read/write, others read)17 int fd2 = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);18 if (fd2 == -1) {19 perror("Error creating file");20 return 1;21 }22 close(fd2);23 24 return 0;25}04read() and write() - File I/O
read() - Read from File Descriptor
write() - Write to File Descriptor
1#include <stdio.h>2#include <fcntl.h>3#include <unistd.h>4#include <string.h>56int main() {7 // Write to stdout using system call8 char *msg = "Hello from write()!\n";9 write(1, msg, strlen(msg)); // 1 = stdout10 11 // Copy file using read/write12 int src = open("source.txt", O_RDONLY);13 int dst = open("copy.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);14 15 if (src == -1 || dst == -1) {16 perror("Error opening files");17 return 1;18 }19 20 char buffer[1024];21 ssize_t bytes;22 23 // Read and write in chunks24 while ((bytes = read(src, buffer, sizeof(buffer))) > 0) {25 write(dst, buffer, bytes);26 }27 28 close(src);29 close(dst);30 printf("File copied!\n");31 32 return 0;33}05close() - Close File Descriptor
close() - Release File Descriptor
Always Close Files!
Each process has a limited number of file descriptors. Not closing files can lead to resource exhaustion and data loss (buffered data may not be written).
06Process System Calls
Why Create Child Processes?
Shell Commands
bash forks to run ls, grep, etc.
Web Servers
Handle multiple clients
Parallel Tasks
Do multiple things at once
How fork() Works — Process Splitting
Parent
PID: 1234
Parent
PID: 1234
fork() returns: 1235
(child's PID)
Child
PID: 1235
fork() returns: 0
(knows it's child)
Key insight: After fork(), both processes continue from the same line. Only the return value differs!
fork() - Create Child Process
getpid() / getppid() - Get Process IDs
pid_t getppid(void); // Parent's PID
1#include <stdio.h>2#include <unistd.h>3#include <sys/wait.h>45int main() {6 printf("Parent PID: %d\n", getpid());7 8 pid_t pid = fork(); // Create child process9 10 if (pid == -1) {11 perror("fork failed");12 return 1;13 }14 else if (pid == 0) {15 // Child process16 printf("Child: My PID = %d, Parent PID = %d\n", 17 getpid(), getppid());18 }19 else {20 // Parent process21 printf("Parent: Created child with PID = %d\n", pid);22 wait(NULL); // Wait for child to finish23 printf("Parent: Child finished\n");24 }25 26 return 0;27}Parent PID: 1234
Parent: Created child with PID = 1235
Child: My PID = 1235, Parent PID = 1234
Parent: Child finished
07exec() Family - Run Programs
How exec() Works — Process Replacement
Your Program
main.c
execlp("ls", ...)
replace code
ls Program
/bin/ls
Key insight: The PID stays the same — exec() replaces your code with new code, but keeps the same process. Code after exec() never runs (unless exec fails)!
exec() Variants Decoded
The name tells you what it does: exec + l/v (args format) + p (search PATH) + e (set environment)
| Function | Args | PATH | Env |
|---|---|---|---|
| execl() | list | No | No |
| execlp() | list | Yes | No |
| execv() | array | No | No |
| execvp() | array | Yes | No |
1#include <stdio.h>2#include <unistd.h>3#include <sys/wait.h>45int main() {6 pid_t pid = fork();7 8 if (pid == 0) {9 // Child: run "ls -la"10 printf("Child running ls...\n");11 execlp("ls", "ls", "-la", NULL); // Search PATH12 13 // Only reached if exec fails14 perror("exec failed");15 return 1;16 }17 else {18 // Parent waits19 wait(NULL);20 printf("Command completed\n");21 }22 23 return 0;24}08Directory Operations
| Function | Prototype | Description |
|---|---|---|
| getcwd() | char *getcwd(char *buf, size_t size) | Get current directory |
| chdir() | int chdir(const char *path) | Change directory |
| mkdir() | int mkdir(const char *path, mode_t mode) | Create directory |
| rmdir() | int rmdir(const char *path) | Remove empty directory |
1#include <stdio.h>2#include <unistd.h>3#include <sys/stat.h>45int main() {6 char cwd[256];7 8 // Get current directory9 if (getcwd(cwd, sizeof(cwd)) != NULL) {10 printf("Current dir: %s\n", cwd);11 }12 13 // Create a new directory14 if (mkdir("mydir", 0755) == 0) {15 printf("Created mydir/\n");16 }17 18 // Change to new directory19 if (chdir("mydir") == 0) {20 getcwd(cwd, sizeof(cwd));21 printf("Now in: %s\n", cwd);22 }23 24 // Go back and remove25 chdir("..");26 rmdir("mydir");27 printf("Removed mydir/\n");28 29 return 0;30}09Error Handling with errno
errno and perror()
When system calls fail, they return -1 and set the global variable errnoto indicate the error. Use perror() to print a human-readable error message.
1#include <stdio.h>2#include <fcntl.h>3#include <unistd.h>4#include <errno.h>5#include <string.h>67int main() {8 int fd = open("nonexistent.txt", O_RDONLY);9 10 if (fd == -1) {11 // Method 1: perror (prints to stderr)12 perror("Error opening file");13 14 // Method 2: strerror (returns string)15 printf("Error: %s\n", strerror(errno));16 17 // Method 3: Check specific error18 if (errno == ENOENT) {19 printf("File not found!\n");20 } else if (errno == EACCES) {21 printf("Permission denied!\n");22 }23 24 return 1;25 }26 27 close(fd);28 return 0;29}Error opening file: No such file or directory
Error: No such file or directory
File not found!
10Library Functions vs System Calls
| Feature | Library (stdio.h) | System Call (unistd.h) |
|---|---|---|
| Handle Type | FILE * | int (fd) |
| Buffering | Yes (buffered) | No (unbuffered) |
| Portability | All platforms | UNIX/Linux only |
| Open | fopen() | open() |
| Read | fread(), fgets() | read() |
| Write | fwrite(), fputs() | write() |
| Close | fclose() | close() |
When to Use What?
Use library functions (stdio.h) for most file I/O — they're portable and buffered. Use system calls when you need low-level control, process creation, or UNIX-specific features.
11Understanding File Permissions
The mode Parameter (0644, 0755, etc.)
When creating files with open() or directories with mkdir(), you specify permissions using octal numbers.
Decoding: 0755
Read (r)
See contents
Write (w)
Modify contents
Execute (x)
Run as program
| Mode | Symbolic | Meaning | Common Use |
|---|---|---|---|
| 0644 | rw-r--r-- | Owner: read+write, Others: read | Regular files |
| 0755 | rwxr-xr-x | Owner: all, Others: read+exec | Executables, directories |
| 0600 | rw------- | Owner only: read+write | Private files, SSH keys |
!Code Pitfalls: Common Mistakes & What to Watch For
Copied code often generates system call code that ignores return values and errno, leading to silent failures and hard-to-debug issues.
If you search online to "write code to read a file using system calls," it might give you code that calls open() and read() without checking if they return -1 (error). This can lead to your program continuing with invalid file descriptors or reading garbage data.
The Trap: Online sources are trained on diverse code, including tutorials that skip error handling for brevity. They often produce "happy path" code that works perfectly when everything goes right but crashes or behaves unexpectedly in real-world scenarios where files don't exist, permissions are denied, or system resources are exhausted.
The Reality: System calls interact directly with the OS kernel and can fail for many reasons. Every system call should have its return value checked, and perror() or strerror(errno) should be used for meaningful error messages. Robust systems programming requires paranoid error handling that Copied code often omits.
13Frequently Asked Questions
Q:What's the difference between library functions and system calls?
A: Library functions (like printf) run in user space and may internally call system calls. System calls (like write) directly request kernel services. System calls have overhead from mode switching but provide direct hardware access.
Q:What is a file descriptor?
A: An integer that the OS uses to identify an open file/resource. 0=stdin, 1=stdout, 2=stderr. When youopen() a file, the kernel returns a new fd. All I/O operations use this number to reference the file.
Q:What does fork() actually do?
A: Creates an exact copy of the current process. Both parent and child continue from the same point, but fork returns 0 to child, child's PID to parent. They have separate memory — changes in one don't affect the other.
Q:Why use system calls instead of stdio functions?
A: More control! System calls let you set file permissions, use non-blocking I/O, work with sockets, manage processes, and do things stdio can't. For simple file I/O, stdio is often sufficient and portable.
13Summary
System Call Quick Reference
File I/O
open(path, flags, mode)
read(fd, buf, count)
write(fd, buf, count)
close(fd)
lseek(fd, offset, whence)
Process
fork()
exec*(path, args...)
wait(status)
getpid() / getppid()
exit(status)
Directory
getcwd(buf, size)
chdir(path)
mkdir(path, mode)
rmdir(path)
opendir() / readdir()
Error Handling
errno (global variable)
perror("message")
strerror(errno)
Check return == -1
The Classic fork() + exec() Pattern
This is how shells run commands — the most important pattern in UNIX:
Key Takeaways
- ✓System calls = direct OS requests (low-level)
- ✓fd = integer handle, 0/1/2 are stdin/stdout/stderr
- ✓fork() = clone process, returns twice
- ✓exec() = replace process code
- ✓Always check return values (-1 = error)
- ✓perror() for human-readable errors
Test Your Knowledge
Related Tutorials
Signals and Signal Handling
Handle UNIX/Linux signals in C! Learn about SIGINT, SIGTERM, SIGSEGV, signal handlers, sigaction, and writing signal-safe code.
File Handling in C
Read and write files! Learn fopen, fclose, fprintf, fscanf, and more. Save data permanently and load it back.
C Preprocessor Directives
Code that runs before compilation! Learn #include, #define macros, and conditional compilation with #ifdef.
Have Feedback?
Found something missing or have ideas to improve this tutorial? Let us know on GitHub!