---
title: "io_uring e I/O Assincrono com Zig: Performance Maxima no Linux"
url: "https://ziglang.com.br/tutoriais/zig-io-uring-async/"
markdown_url: "https://ziglang.com.br/tutoriais/zig-io-uring-async.MD"
description: "Domine io_uring com Zig para I/O assincrono de alta performance. Configuracao, operacoes assincronas, batching, chaining e construcao de servidor de alta performance. Tutorial completo em portugues."
date: "2026-02-21"
author: ""
---

# io_uring e I/O Assincrono com Zig: Performance Maxima no Linux

Domine io_uring com Zig para I/O assincrono de alta performance. Configuracao, operacoes assincronas, batching, chaining e construcao de servidor de alta performance. Tutorial completo em portugues.


io_uring e a interface de I/O assincrono mais moderna e performatica do kernel Linux, introduzida na versao 5.1 (2019). Ela revoluciona a forma como programas interagem com o kernel, eliminando overheads de syscalls tradicionais e permitindo batching de operacoes. Neste artigo final da serie de Sistemas Operacionais, exploramos como Zig se integra com io_uring para criar aplicacoes de performance maxima.

> Pre-requisitos: Este artigo assume familiaridade com os conceitos dos artigos anteriores, especialmente [syscalls](/tutoriais/zig-sistemas-operacionais/artigo-1-syscalls-linux/) e [networking](/tutoriais/zig-sistemas-operacionais/artigo-4-networking-sockets-raw/). Para uma introducao ao io_uring em Zig, veja tambem [Zig Async com io_uring](/tutoriais/zig-async-iouring/).

## Por Que io_uring?

O modelo tradicional de I/O no Linux tem limitacoes fundamentais:

| Modelo | Problema |
|--------|----------|
| **Blocking I/O** | Thread fica bloqueada esperando. Nao escala. |
| **Non-blocking + poll/select** | Muitas syscalls. Overhead de copia de dados. |
| **epoll** | Melhor que poll, mas ainda 1 syscall por evento. |
| **AIO (POSIX)** | Implementacao ruim no Linux. Limitado a direct I/O. |
| **io_uring** | Zero syscalls no caminho quente. Batching nativo. Suporta tudo. |

io_uring funciona atraves de dois buffers circulares (ring buffers) compartilhados entre user space e kernel space:

1. **Submission Queue (SQ):** Voce coloca requisicoes aqui
2. **Completion Queue (CQ):** O kernel coloca resultados aqui

Como ambos sao memoria compartilhada, nao ha copia de dados entre user space e kernel space — a comunicacao e feita por escrita direta na memoria.

## Configurando io_uring em Zig

A standard library do Zig inclui bindings para io_uring:

```zig
const std = @import("std");
const linux = std.os.linux;
const IoUring = linux.IoUring;

pub fn main() !void {
    // Criar io_uring com 256 entradas
    var ring = try IoUring.init(256, 0);
    defer ring.deinit();

    std.debug.print("io_uring inicializado com sucesso!\n", .{});
    std.debug.print("SQ entries: {d}\n", .{ring.sq.sqes.len});
    std.debug.print("CQ entries: {d}\n", .{ring.cq.cqes.len});
}
```

## Operacoes Basicas com io_uring

### Leitura Assincrona de Arquivo

```zig
const std = @import("std");
const linux = std.os.linux;
const IoUring = linux.IoUring;

pub fn main() !void {
    var ring = try IoUring.init(32, 0);
    defer ring.deinit();

    // Abrir arquivo
    const arquivo = try std.fs.cwd().openFile("/etc/hostname", .{});
    defer arquivo.close();

    // Preparar buffer
    var buffer: [1024]u8 = undefined;

    // Submeter operacao de leitura
    const sqe = try ring.get_sqe();
    sqe.prep_read(arquivo.handle, &buffer, 0);
    sqe.user_data = 42; // Identificador customizado

    // Submeter para o kernel
    _ = try ring.submit();

    // Aguardar conclusao
    const cqe = try ring.copy_cqe();

    if (cqe.res > 0) {
        const bytes_lidos: usize = @intCast(cqe.res);
        std.debug.print("Lido ({d} bytes): {s}\n", .{
            bytes_lidos,
            buffer[0..bytes_lidos],
        });
    } else {
        std.debug.print("Erro na leitura: {d}\n", .{cqe.res});
    }
}
```

### Escrita Assincrona

```zig
const std = @import("std");
const linux = std.os.linux;
const IoUring = linux.IoUring;

pub fn main() !void {
    var ring = try IoUring.init(32, 0);
    defer ring.deinit();

    const arquivo = try std.fs.cwd().createFile("saida_async.txt", .{});
    defer arquivo.close();

    const dados = "Escrito de forma assincrona via io_uring!\n";

    // Submeter escrita
    const sqe = try ring.get_sqe();
    sqe.prep_write(arquivo.handle, dados, 0);
    sqe.user_data = 1;

    _ = try ring.submit();

    // Aguardar conclusao
    const cqe = try ring.copy_cqe();

    if (cqe.res >= 0) {
        std.debug.print("Escrita concluida: {d} bytes\n", .{cqe.res});
    } else {
        std.debug.print("Erro na escrita: {d}\n", .{cqe.res});
    }
}
```

## Batching: Multiplas Operacoes Simultaneas

O verdadeiro poder do io_uring esta no batching — submeter multiplas operacoes com uma unica syscall (ou nenhuma, no modo SQPOLL).

```zig
const std = @import("std");
const linux = std.os.linux;
const IoUring = linux.IoUring;

pub fn main() !void {
    var ring = try IoUring.init(64, 0);
    defer ring.deinit();

    const arquivos = [_][]const u8{
        "/etc/hostname",
        "/etc/os-release",
        "/proc/uptime",
        "/proc/loadavg",
    };

    var buffers: [arquivos.len][1024]u8 = undefined;
    var fds: [arquivos.len]std.posix.fd_t = undefined;

    // Abrir todos os arquivos
    for (arquivos, 0..) |nome, i| {
        const f = try std.fs.openFileAbsolute(nome, .{});
        fds[i] = f.handle;
    }
    defer for (fds) |fd| std.posix.close(fd);

    // Submeter TODAS as leituras de uma vez
    for (0..arquivos.len) |i| {
        const sqe = try ring.get_sqe();
        sqe.prep_read(fds[i], &buffers[i], 0);
        sqe.user_data = @intCast(i);
    }

    // Uma unica syscall para submeter tudo
    const submetidos = try ring.submit();
    std.debug.print("Submetidas {d} operacoes com 1 syscall\n", .{submetidos});

    // Coletar resultados
    var completados: usize = 0;
    while (completados < arquivos.len) {
        const cqe = try ring.copy_cqe();
        const idx: usize = @intCast(cqe.user_data);

        if (cqe.res > 0) {
            const bytes: usize = @intCast(cqe.res);
            std.debug.print("\n=== {s} ({d} bytes) ===\n{s}\n", .{
                arquivos[idx],
                bytes,
                buffers[idx][0..bytes],
            });
        }

        completados += 1;
    }
}
```

## Servidor de Alta Performance com io_uring

Vamos construir um servidor HTTP minimalista usando io_uring para I/O assincrono:

```zig
const std = @import("std");
const linux = std.os.linux;
const posix = std.posix;
const IoUring = linux.IoUring;
const net = std.net;

const RESPOSTA_HTTP =
    "HTTP/1.1 200 OK\r\n" ++
    "Content-Type: text/plain\r\n" ++
    "Content-Length: 19\r\n" ++
    "Connection: close\r\n" ++
    "\r\n" ++
    "Ola via io_uring!\n";

const OpType = enum(u8) {
    accept,
    read,
    write,
    close,
};

const UserData = packed struct {
    fd: i32,
    op: OpType,
};

fn encodeUserData(fd: i32, op: OpType) u64 {
    const ud = UserData{ .fd = fd, .op = op };
    return @bitCast(ud);
}

fn decodeUserData(data: u64) UserData {
    return @bitCast(data);
}

pub fn main() !void {
    var ring = try IoUring.init(256, 0);
    defer ring.deinit();

    // Criar socket de escuta
    const listen_fd = try posix.socket(posix.AF.INET, posix.SOCK.STREAM, 0);
    defer posix.close(listen_fd);

    try posix.setsockopt(listen_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)));

    const addr = net.Address.initIp4(.{ 0, 0, 0, 0 }, 8080);
    try posix.bind(listen_fd, &addr.any, addr.getOsSockLen());
    try posix.listen(listen_fd, 128);

    std.debug.print("Servidor io_uring escutando em 0.0.0.0:8080\n", .{});

    // Submeter primeiro accept
    var client_addr: posix.sockaddr = undefined;
    var addr_len: posix.socklen_t = @sizeOf(posix.sockaddr);

    {
        const sqe = try ring.get_sqe();
        sqe.prep_accept(listen_fd, &client_addr, &addr_len, 0);
        sqe.user_data = encodeUserData(listen_fd, .accept);
    }
    _ = try ring.submit();

    var read_buffers: [1024][1024]u8 = undefined;
    var buffer_idx: usize = 0;

    // Event loop
    while (true) {
        const cqe = try ring.copy_cqe();
        const ud = decodeUserData(cqe.user_data);

        switch (ud.op) {
            .accept => {
                if (cqe.res >= 0) {
                    const client_fd: i32 = cqe.res;

                    // Submeter leitura do cliente
                    const idx = buffer_idx % read_buffers.len;
                    buffer_idx += 1;

                    const read_sqe = try ring.get_sqe();
                    read_sqe.prep_read(client_fd, &read_buffers[idx], 0);
                    read_sqe.user_data = encodeUserData(client_fd, .read);
                }

                // Submeter proximo accept
                const accept_sqe = try ring.get_sqe();
                accept_sqe.prep_accept(listen_fd, &client_addr, &addr_len, 0);
                accept_sqe.user_data = encodeUserData(listen_fd, .accept);

                _ = try ring.submit();
            },
            .read => {
                if (cqe.res > 0) {
                    // Submeter resposta
                    const write_sqe = try ring.get_sqe();
                    write_sqe.prep_write(ud.fd, RESPOSTA_HTTP, 0);
                    write_sqe.user_data = encodeUserData(ud.fd, .write);
                    _ = try ring.submit();
                } else {
                    posix.close(ud.fd);
                }
            },
            .write => {
                // Fechar conexao apos resposta
                posix.close(ud.fd);
            },
            .close => {},
        }
    }
}
```

## SQPOLL: Zero Syscalls

O modo SQPOLL cria uma thread do kernel que monitora a submission queue. Isso elimina completamente a necessidade de syscalls para submeter operacoes:

```zig
const std = @import("std");
const linux = std.os.linux;
const IoUring = linux.IoUring;

pub fn main() !void {
    // SQPOLL: kernel thread monitora a SQ
    // Requer CAP_SYS_NICE ou root
    var ring = try IoUring.init(256, linux.IORING_SETUP_SQPOLL);
    defer ring.deinit();

    std.debug.print("io_uring com SQPOLL ativo!\n", .{});
    std.debug.print("Nenhuma syscall necessaria para submeter operacoes.\n", .{});

    // Operacoes submetidas aqui sao processadas
    // pela kernel thread automaticamente
    const arquivo = try std.fs.cwd().openFile("/proc/uptime", .{});
    defer arquivo.close();

    var buffer: [256]u8 = undefined;
    const sqe = try ring.get_sqe();
    sqe.prep_read(arquivo.handle, &buffer, 0);
    sqe.user_data = 1;

    // Nao precisa chamar submit() - a kernel thread vai detectar
    // Mas podemos forcar para nao esperar
    _ = try ring.submit();

    const cqe = try ring.copy_cqe();
    if (cqe.res > 0) {
        const n: usize = @intCast(cqe.res);
        std.debug.print("Uptime: {s}\n", .{buffer[0..n]});
    }
}
```

## Benchmarks: io_uring vs Alternativas

Resultados tipicos em um servidor com muitas conexoes simultaneas:

| Metrica | epoll | io_uring | io_uring + SQPOLL |
|---------|-------|----------|-------------------|
| **Requisicoes/s** | 150K | 280K | 320K |
| **Latencia p99** | 2.1ms | 0.8ms | 0.5ms |
| **Syscalls/req** | 3-4 | 0-1 | 0 |
| **CPU usage** | 65% | 45% | 40% |

Os numeros variam conforme hardware e carga, mas io_uring consistentemente supera epoll, especialmente sob alta concorrencia.

## Boas Praticas com io_uring

1. **Dimensione o ring adequadamente:** Comece com 256-1024 entradas e ajuste conforme a carga.

2. **Use user_data para rastrear operacoes:** Codifique o tipo de operacao e contexto no campo `user_data`.

3. **Batching sempre que possivel:** Submeta multiplas operacoes antes de chamar `submit()`.

4. **SQPOLL para latencia minima:** Use em cenarios onde microsegundos importam (trading, games).

5. **Fixed buffers e files:** Para performance maxima, pre-registre buffers e file descriptors com o kernel.

## Exercicios

1. **File copy assincrono:** Implemente uma copia de arquivo grande usando io_uring com buffers duplos (leitura e escrita simultaneous).

2. **Proxy TCP:** Crie um proxy TCP simples que use io_uring para gerenciar conexoes de entrada e saida simultaneamente.

3. **Benchmark comparativo:** Compare a performance de leitura de muitos arquivos pequenos usando read() sincrono vs io_uring batched.

---

## Conclusao da Serie

Esta serie cobriu os fundamentos da programacao de sistemas com Zig:

1. [Syscalls Linux](/tutoriais/zig-sistemas-operacionais/artigo-1-syscalls-linux/) — A interface com o kernel
2. [File System Operations](/tutoriais/zig-sistemas-operacionais/artigo-2-file-system-operations/) — Arquivos e diretorios
3. [Processos e Signals](/tutoriais/zig-sistemas-operacionais/artigo-3-processos-signals/) — Gerenciamento de processos
4. [Networking com Sockets Raw](/tutoriais/zig-sistemas-operacionais/artigo-4-networking-sockets-raw/) — Comunicacao de rede
5. [io_uring e I/O Assincrono](/tutoriais/zig-sistemas-operacionais/artigo-5-io-uring-async/) — Performance maxima (este artigo)

### Conteudo Relacionado

- [Zig Async com io_uring](/tutoriais/zig-async-iouring/) — Introducao ao io_uring
- [Otimizacao de Performance em Zig](/tutoriais/zig-performance/) — Serie de performance
- [Profiling e Benchmarks em Zig](/tutoriais/zig-profiling-benchmarks/) — Ferramentas de medicao
- [Zig em Producao: Case Studies](/artigos/zig-em-producao-case-studies/) — Exemplos reais

---

*Concluiu a serie? Parabens! Voce agora tem uma base solida em programacao de sistemas com Zig. Continue explorando com nossas outras series e tutoriais.*
