Syscalls sao a interface fundamental entre seus programas e o kernel do sistema operacional. Toda operacao que envolve recursos do sistema — abrir arquivos, alocar memoria, criar processos, enviar dados pela rede — passa por uma syscall. Neste primeiro artigo da serie Programacao de Sistemas com Zig, vamos explorar como Zig facilita o uso direto de syscalls Linux, oferecendo uma experiencia muito mais segura e ergonomica do que C.
Este artigo assume que voce ja tem familiaridade com a sintaxe basica de Zig. Se voce esta comecando, confira primeiro a serie Zig para Iniciantes.
O Que Sao Syscalls?
Quando um programa em user space precisa interagir com hardware ou recursos gerenciados pelo kernel, ele nao pode fazer isso diretamente. Em vez disso, ele faz uma chamada de sistema (syscall), que e uma requisicao ao kernel para executar uma operacao privilegiada.
O fluxo e o seguinte:
- O programa coloca o numero da syscall em um registrador (rax no x86_64)
- Os argumentos sao colocados em registradores especificos (rdi, rsi, rdx, r10, r8, r9)
- A instrucao
syscalle executada, transferindo controle ao kernel - O kernel executa a operacao e retorna o resultado em rax
Em C, voce tipicamente usa wrappers da libc como read(), write(), open(). Em Zig, voce tem acesso direto as syscalls atraves do modulo std.os.linux e tambem pode usar a standard library de forma mais ergonomica.
Syscalls Diretas em Zig
Zig permite invocar syscalls diretamente sem depender da libc. Isso e particularmente util para sistemas embarcados, binarios estaticos minimos ou quando voce precisa de controle total.
Exemplo: write syscall direta
const std = @import("std");
const linux = std.os.linux;
pub fn main() void {
const mensagem = "Ola direto do kernel!\n";
// syscall write: fd=1 (stdout), buffer, tamanho
const resultado = linux.syscall3(
.write,
1, // file descriptor 1 = stdout
@intFromPtr(mensagem.ptr),
mensagem.len,
);
if (resultado < 0) {
// Erro na syscall
std.debug.print("Erro na syscall write: {d}\n", .{resultado});
}
}
Observe que estamos chamando linux.syscall3 diretamente, passando o numero da syscall (.write) e seus tres argumentos. O “3” em syscall3 indica o numero de argumentos.
Entendendo os Numeros de Syscall
Zig define todas as syscalls Linux como um enum em std.os.linux.SYS. Voce pode usar nomes simbolicos em vez de numeros magicos:
const linux = std.os.linux;
// Em vez de usar numeros magicos:
// const SYS_write = 1; // Estilo C
// Zig usa um enum tipado:
const syscall_write = linux.SYS.write; // = 1 no x86_64
const syscall_read = linux.SYS.read; // = 0 no x86_64
const syscall_open = linux.SYS.open; // = 2 no x86_64
const syscall_close = linux.SYS.close; // = 3 no x86_64
Isso torna o codigo portavel entre arquiteturas, ja que os numeros de syscall podem variar entre x86_64, aarch64, etc.
A Standard Library: O Caminho Recomendado
Embora syscalls diretas sejam educativas, para codigo de producao a standard library de Zig oferece wrappers muito mais seguros e ergonomicos. Esses wrappers cuidam do tratamento de erros, conversao de tipos e compatibilidade entre plataformas.
Abrindo e Lendo Arquivos
const std = @import("std");
pub fn main() !void {
// Abrir arquivo para leitura
const arquivo = try std.fs.cwd().openFile("/etc/hostname", .{});
defer arquivo.close();
// Ler conteudo em um buffer
var buffer: [1024]u8 = undefined;
const bytes_lidos = try arquivo.read(&buffer);
const conteudo = buffer[0..bytes_lidos];
std.debug.print("Hostname: {s}", .{conteudo});
}
Compare com o equivalente em C:
// Versao C - mais verbosa e propensa a erros
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd = open("/etc/hostname", O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}
char buffer[1024];
ssize_t n = read(fd, buffer, sizeof(buffer));
if (n == -1) {
perror("read");
close(fd); // Facil esquecer!
return 1;
}
buffer[n] = '\0'; // Facil esquecer!
printf("Hostname: %s", buffer);
close(fd); // Facil esquecer!
return 0;
}
Em Zig, defer arquivo.close() garante que o arquivo sera fechado nao importa o que aconteca. O operador try propaga erros automaticamente. Nao ha risco de esquecer o close() ou de nao verificar erros.
Escrevendo em Arquivos
const std = @import("std");
pub fn main() !void {
// Criar ou truncar arquivo
const arquivo = try std.fs.cwd().createFile("saida.txt", .{});
defer arquivo.close();
// Escrever conteudo
try arquivo.writeAll("Primeira linha\n");
try arquivo.writeAll("Segunda linha\n");
// Usar writer formatado
const writer = arquivo.writer();
try writer.print("Valor: {d}\n", .{42});
try writer.print("Data: {s}\n", .{"2026-02-21"});
}
Syscalls Essenciais para Programadores Zig
Vamos explorar as syscalls mais importantes que todo programador de sistemas precisa conhecer.
open, read, write, close — O Quarteto Fundamental
Estas quatro syscalls formam a base de toda operacao de I/O no Linux. Tudo e um arquivo no Unix: dispositivos, pipes, sockets e arquivos regulares sao todos manipulados com essas syscalls.
const std = @import("std");
const posix = std.posix;
pub fn main() !void {
// open: abre um file descriptor
const fd = try posix.open("/tmp/teste_syscall.txt", .{
.ACCMODE = .WRONLY,
.CREAT = true,
.TRUNC = true,
}, 0o644);
defer posix.close(fd);
// write: escreve bytes no file descriptor
const dados = "Escrito via posix.open!\n";
const bytes_escritos = try posix.write(fd, dados);
std.debug.print("Bytes escritos: {d}\n", .{bytes_escritos});
}
mmap — Mapeamento de Memoria
mmap e uma das syscalls mais poderosas do Linux. Ela mapeia arquivos ou memoria anonima diretamente no espaco de enderecamento do processo.
const std = @import("std");
const posix = std.posix;
pub fn main() !void {
const arquivo = try std.fs.cwd().openFile("dados.bin", .{});
defer arquivo.close();
const stat = try arquivo.stat();
const tamanho = stat.size;
// Mapear arquivo na memoria
const mapeado = try posix.mmap(
null,
tamanho,
posix.PROT.READ,
.{ .TYPE = .SHARED },
arquivo.handle,
0,
);
defer posix.munmap(mapeado);
// Agora podemos acessar o arquivo como um slice de bytes
const bytes: []const u8 = @as([*]const u8, @ptrCast(mapeado))[0..tamanho];
std.debug.print("Primeiros 100 bytes: {s}\n", .{bytes[0..@min(100, bytes.len)]});
}
mmap e extremamente eficiente para arquivos grandes porque o kernel carrega as paginas sob demanda (lazy loading), e multiplos processos podem compartilhar a mesma memoria fisica.
ioctl — Controle de Dispositivos
ioctl e a syscall “coringa” do Linux, usada para operacoes especificas de dispositivos que nao se encaixam no modelo read/write.
const std = @import("std");
const posix = std.posix;
const linux = std.os.linux;
const TIOCGWINSZ = 0x5413;
const Winsize = extern struct {
ws_row: u16,
ws_col: u16,
ws_xpixel: u16,
ws_ypixel: u16,
};
pub fn obterTamanhoTerminal() !Winsize {
var ws: Winsize = undefined;
const resultado = linux.ioctl(
std.io.getStdOut().handle,
TIOCGWINSZ,
@intFromPtr(&ws),
);
if (resultado != 0) return error.IoctlFailed;
return ws;
}
pub fn main() !void {
const tamanho = try obterTamanhoTerminal();
std.debug.print("Terminal: {d} linhas x {d} colunas\n", .{
tamanho.ws_row,
tamanho.ws_col,
});
}
Tratamento de Erros em Syscalls
Uma das maiores vantagens de Zig sobre C para programacao de sistemas e o tratamento de erros. Em C, voce precisa verificar manualmente o valor de retorno de cada syscall e consultar errno. Em Zig, o sistema de error unions torna isso natural e impossivel de esquecer.
Padrao em C (propenso a erros)
int fd = open("arquivo.txt", O_RDONLY);
if (fd == -1) {
// Programador pode esquecer esta verificacao
perror("open");
return 1;
}
// Se esquecer o if, o programa continua com fd = -1
Padrao em Zig (seguro por design)
const std = @import("std");
pub fn main() !void {
// Se open falhar, o erro e propagado automaticamente com try
const arquivo = try std.fs.cwd().openFile("arquivo.txt", .{});
defer arquivo.close();
// Ou trate o erro explicitamente
const arquivo2 = std.fs.cwd().openFile("outro.txt", .{}) catch |err| {
std.debug.print("Erro ao abrir: {}\n", .{err});
return;
};
defer arquivo2.close();
}
O compilador Zig exige que voce lide com cada possivel erro. Voce nao pode simplesmente ignorar o retorno de uma funcao que pode falhar. Isso elimina uma classe inteira de bugs que sao comuns em programas C.
Criando Wrappers Seguros
Para projetos maiores, e uma boa pratica criar wrappers que adicionam contexto e logging as operacoes de syscall.
const std = @import("std");
const SysError = error{
PermissaoNegada,
ArquivoNaoEncontrado,
SemEspaco,
ErroDesconhecido,
};
pub fn lerArquivoCompleto(
allocator: std.mem.Allocator,
caminho: []const u8,
) ![]u8 {
const arquivo = std.fs.cwd().openFile(caminho, .{}) catch |err| {
std.log.err("Falha ao abrir '{s}': {}", .{ caminho, err });
return switch (err) {
error.FileNotFound => SysError.ArquivoNaoEncontrado,
error.AccessDenied => SysError.PermissaoNegada,
else => SysError.ErroDesconhecido,
};
};
defer arquivo.close();
const conteudo = try arquivo.readToEndAlloc(allocator, 10 * 1024 * 1024);
std.log.info("Lido {d} bytes de '{s}'", .{ conteudo.len, caminho });
return conteudo;
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const conteudo = try lerArquivoCompleto(allocator, "/etc/os-release");
defer allocator.free(conteudo);
std.debug.print("{s}\n", .{conteudo});
}
Comparacao: Zig vs C para Syscalls
| Aspecto | C | Zig |
|---|---|---|
| Verificacao de erros | Manual, facil de esquecer | Obrigatoria pelo compilador |
| Limpeza de recursos | Manual (close, free) | Automatica com defer |
| Portabilidade | Depende da libc | Pode compilar sem libc |
| Seguranca de tipos | Fraca (void*, int para tudo) | Forte (enums, error unions) |
| Buffer overflows | Faceis de causar | Verificacao em runtime |
| Cross-compilation | Complexa (toolchains) | zig build -Dtarget=... |
Exercicios Praticos
Exercicio 1: Informacoes do Sistema
Crie um programa que leia e exiba o conteudo de /proc/cpuinfo e /proc/meminfo, extraindo o modelo do processador e a memoria total.
Exercicio 2: Copia de Arquivos
Implemente uma funcao que copie um arquivo para outro usando buffers de 4096 bytes, exibindo o progresso durante a copia.
Exercicio 3: Listagem de Diretorios
Use a standard library para listar todos os arquivos de um diretorio, mostrando nome, tamanho e permissoes.
Proximo Artigo
No proximo artigo, vamos nos aprofundar em operacoes de file system, explorando manipulacao de diretorios, permissoes, file locking e memory-mapped files.
Conteudo Relacionado
- Zig para Programadores C — Transicao de C para Zig
- Gerenciamento de Memoria em Zig — Allocators e ponteiros
- Tratamento de Erros em Zig — Error unions em profundidade
- Zig e Interoperabilidade com C — Chamando codigo C de Zig
Tem duvidas sobre syscalls em Zig? Deixe um comentario ou entre em contato com a comunidade Zig Brasil.