# System Calls

### System Calls

A system call is a request from your program to the operating system.

Your program cannot directly do everything it wants. It cannot directly open files, create processes, read from the network, allocate virtual memory pages, or talk to hardware. Those operations belong to the operating system.

So your program asks.

That request is called a system call.

A normal function call stays inside your program:

```zig
const x = add(10, 20);
```

A system call crosses from your program into the operating system kernel:

```zig
const n = try std.posix.read(fd, buffer);
```

Here, your program asks the operating system to read bytes from a file descriptor.

#### Why System Calls Exist

Modern operating systems protect programs from each other.

One program should not be able to read another program's memory. One program should not be able to write to arbitrary disk locations. One program should not be able to control hardware directly without permission.

The operating system kernel sits between programs and the machine.

Your program runs in user space. The kernel runs in kernel space.

User space is where normal application code runs.

Kernel space is where the operating system handles protected work: files, memory, processes, devices, networking, and permissions.

A system call is the controlled doorway between these two worlds.

#### Common System Calls

You do not need to memorize all system calls now. Start with the common families:

| Area | Examples | What They Do |
|---|---|---|
| Files | `open`, `read`, `write`, `close` | Work with files and file descriptors |
| Memory | `mmap`, `munmap` | Map and unmap virtual memory |
| Processes | `fork`, `exec`, `wait` | Create and manage processes |
| Time | `clock_gettime`, `nanosleep` | Read clocks and sleep |
| Networking | `socket`, `bind`, `listen`, `accept`, `connect` | Work with network connections |
| Metadata | `stat`, `fstat` | Read information about files |

In Zig, many of these are exposed through `std.posix` on POSIX-like systems.

#### File Descriptors

On POSIX systems such as Linux and macOS, many system calls work with file descriptors.

A file descriptor is a small integer that represents an open resource.

That resource might be:

a file

a directory

a socket

a pipe

a terminal

standard input

standard output

standard error

The usual standard file descriptors are:

| File Descriptor | Name | Meaning |
|---:|---|---|
| `0` | standard input | Where input usually comes from |
| `1` | standard output | Where normal output usually goes |
| `2` | standard error | Where error output usually goes |

When you write this:

```zig
std.debug.print("hello\n", .{});
```

Zig eventually writes bytes to an output stream, and that stream is backed by an operating system resource.

At the low level, output is a request to the operating system.

#### Calling a Low-Level Write

Here is a small example using a POSIX write call directly:

```zig
const std = @import("std");

pub fn main() !void {
    const message = "Hello from a system call\n";

    const stdout = std.posix.STDOUT_FILENO;
    _ = try std.posix.write(stdout, message);
}
```

This writes bytes to standard output.

The call:

```zig
try std.posix.write(stdout, message);
```

asks the operating system to write `message` to file descriptor `1`.

The return value is the number of bytes written.

#### Partial Writes

A write call does not always write every byte you give it.

This surprises beginners.

For regular files, writes often complete fully. For pipes, sockets, terminals, and non-blocking file descriptors, a write may write only part of the buffer.

So low-level code must be prepared for partial writes.

A safer helper loops until all bytes are written:

```zig
const std = @import("std");

fn writeAll(fd: std.posix.fd_t, bytes: []const u8) !void {
    var remaining = bytes;

    while (remaining.len > 0) {
        const n = try std.posix.write(fd, remaining);
        remaining = remaining[n..];
    }
}

pub fn main() !void {
    try writeAll(std.posix.STDOUT_FILENO, "hello\n");
}
```

This function keeps writing until the slice is empty.

The important line is:

```zig
remaining = remaining[n..];
```

After writing `n` bytes, the program removes those bytes from the front of the slice and continues with the rest.

#### Reading from Standard Input

Reading is the reverse operation.

```zig
const std = @import("std");

pub fn main() !void {
    var buffer: [1024]u8 = undefined;

    const stdin = std.posix.STDIN_FILENO;
    const n = try std.posix.read(stdin, &buffer);

    const bytes = buffer[0..n];
    try writeAll(std.posix.STDOUT_FILENO, bytes);
}

fn writeAll(fd: std.posix.fd_t, bytes: []const u8) !void {
    var remaining = bytes;

    while (remaining.len > 0) {
        const n = try std.posix.write(fd, remaining);
        remaining = remaining[n..];
    }
}
```

This program reads up to 1024 bytes from standard input, then writes those bytes back to standard output.

If you run it and type text, it echoes the text back.

#### End of File

A read call returns `0` when it reaches end of file.

For a regular file, this means there are no more bytes.

For standard input, it may mean the input stream has been closed.

A common read loop looks like this:

```zig
const std = @import("std");

pub fn main() !void {
    var buffer: [4096]u8 = undefined;

    while (true) {
        const n = try std.posix.read(std.posix.STDIN_FILENO, &buffer);

        if (n == 0) {
            break;
        }

        try writeAll(std.posix.STDOUT_FILENO, buffer[0..n]);
    }
}

fn writeAll(fd: std.posix.fd_t, bytes: []const u8) !void {
    var remaining = bytes;

    while (remaining.len > 0) {
        const n = try std.posix.write(fd, remaining);
        remaining = remaining[n..];
    }
}
```

This is the basic shape of many Unix-style programs:

read bytes

process bytes

write bytes

repeat until end of file

#### System Calls Can Fail

System calls interact with the outside world, so they fail often.

A file may not exist.

A permission check may fail.

A disk may be full.

A network connection may close.

A signal may interrupt a call.

A file descriptor may be invalid.

In Zig, these failures become errors. That is why many low-level calls return error unions.

```zig
const n = try std.posix.read(fd, buffer);
```

The `try` means: if the read fails, return the error from the current function.

You can also handle the error directly:

```zig
const n = std.posix.read(fd, buffer) catch |err| {
    std.debug.print("read failed: {}\n", .{err});
    return err;
};
```

For systems programming, this explicit style is useful. It keeps failure visible.

#### System Calls Are Expensive Compared with Normal Function Calls

A normal function call is cheap.

A system call is more expensive because the CPU must cross from user space into kernel space. The operating system must check permissions, inspect arguments, perform the operation, and return control to your program.

This does not mean system calls are bad. They are necessary.

But it does mean you should avoid unnecessary system calls in hot loops.

For example, this is inefficient:

```zig
for (bytes) |b| {
    _ = try std.posix.write(std.posix.STDOUT_FILENO, bytes[index..index + 1]);
}
```

Writing one byte at a time causes many system calls.

This is better:

```zig
_ = try std.posix.write(std.posix.STDOUT_FILENO, bytes);
```

One larger write is usually better than many tiny writes.

#### Use `std` First, Use `std.posix` When Needed

Most Zig programs should start with higher-level standard library APIs.

For example, instead of calling POSIX `open` directly, you can use:

```zig
var file = try std.fs.cwd().openFile("data.txt", .{});
defer file.close();
```

Instead of manually calling low-level write, you can use writers:

```zig
const stdout = std.io.getStdOut().writer();
try stdout.print("hello {s}\n", .{"zig"});
```

These APIs are easier to read and more portable.

Use `std.posix` when you need lower-level control or when you are learning how the operating system works.

#### Portability

System calls are operating-system specific.

Linux, macOS, Windows, and other systems expose different kernel interfaces. POSIX gives Unix-like systems a shared style, but details still vary.

Zig helps by providing standard library abstractions where possible.

For portable code, prefer:

```zig
std.fs
std.io
std.process
std.net
std.Thread
```

For OS-specific code, use lower-level modules such as:

```zig
std.posix
std.os.linux
std.os.windows
```

The deeper you go, the more you need to know about the target operating system.

#### Mental Model

A system call is not just another library function.

It is a boundary crossing.

Your program asks the operating system to do protected work. The operating system checks the request, performs the operation if allowed, and returns either a result or an error.

For Zig programmers, this model fits naturally. Zig already makes errors explicit, memory explicit, and control flow explicit. System calls follow the same principle: the important parts should be visible in the code.

