POSIX: portabilidade entre sistemas Unix

1. O que é POSIX e por que ele importa para programadores C

POSIX (Portable Operating System Interface) é o padrão IEEE 1003.1, originalmente desenvolvido nos anos 1980 para unificar as diversas variantes do Unix. Para programadores C, POSIX representa a ponte entre escrever código que funciona em Linux, macOS, BSDs e outros sistemas Unix-like sem modificações significativas.

Enquanto o ISO C padrão define um núcleo comum da linguagem (funções como fopen, fread, fwrite), o POSIX adiciona aproximadamente 1.200 funções extras que lidam com processos, sinais, threads, IPC e E/S de baixo nível. A diferença crucial: ISO C é portável entre qualquer sistema com um compilador C, mas POSIX é portável apenas entre sistemas Unix-like — porém oferece capacidades que o C padrão simplesmente não possui.

2. Principais headers POSIX que todo programador C deve conhecer

#include <unistd.h>    /* Chamadas de sistema: read, write, fork, exec */
#include <fcntl.h>     /* Controle de arquivos: open, fcntl, flags */
#include <sys/types.h> /* Tipos primitivos: pid_t, size_t, off_t */
#include <sys/stat.h>  /* Atributos de arquivos: stat, chmod */
#include <dirent.h>    /* Manipulação de diretórios: opendir, readdir */

O header unistd.h é o mais icônico — ele expõe as funções que fazem um sistema operacional ser Unix. fcntl.h permite controle fino sobre descritores de arquivo. sys/types.h define tipos como pid_t (identificador de processo) e off_t (posição em arquivo). dirent.h torna trivial navegar por diretórios.

3. API de arquivos e E/S com POSIX vs. C padrão

A diferença fundamental é que POSIX trabalha com descritores de arquivo (inteiros), enquanto C padrão usa FILE* streams:

/* POSIX: baixo nível, sem buffer */
#include <fcntl.h>
#include <unistd.h>

int fd = open("dados.bin", O_RDONLY);
if (fd == -1) { perror("open"); return 1; }

char buffer[1024];
ssize_t bytes_lidos = read(fd, buffer, sizeof(buffer));
close(fd);

/* C padrão: bufferizado */
#include <stdio.h>

FILE* fp = fopen("dados.bin", "rb");
if (!fp) { perror("fopen"); return 1; }

char buffer[1024];
size_t bytes_lidos = fread(buffer, 1, sizeof(buffer), fp);
fclose(fp);

Funções POSIX como open e read oferecem controle mais fino: flags de abertura (O_CREAT, O_EXCL, O_NONBLOCK), operações atômicas e acesso a dispositivos. lseek permite posicionamento em arquivos maiores que 2GB (com off64_t). mmap mapeia arquivos diretamente na memória, algo impossível com C padrão.

4. Processos, sinais e threads no padrão POSIX

Processos são a espinha dorsal do Unix. O padrão POSIX define:

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

pid_t pid = fork();
if (pid == 0) {
    /* Processo filho */
    execlp("/bin/ls", "ls", "-l", NULL);
    _exit(1);  /* Só chega aqui se exec falhar */
} else if (pid > 0) {
    /* Processo pai */
    int status;
    waitpid(pid, &status, 0);
}

Sinais permitem comunicação assíncrona. sigaction é a versão POSIX moderna e portável:

#include <signal.h>

void handler(int sig) {
    write(STDOUT_FILENO, "Sinal recebido\n", 15);
}

struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);

Threads POSIX (pthreads) são padronizadas e funcionam em todos os sistemas Unix:

#include <pthread.h>

void* tarefa(void* arg) {
    printf("Thread executando\n");
    return NULL;
}

pthread_t thread;
pthread_create(&thread, NULL, tarefa, NULL);
pthread_join(thread, NULL);

5. Comunicação entre processos (IPC) portável

Pipes são o mecanismo IPC mais básico e portável:

int pipefd[2];
pipe(pipefd);

if (fork() == 0) {
    close(pipefd[0]);  /* Fecha leitura */
    write(pipefd[1], "mensagem", 8);
    close(pipefd[1]);
    _exit(0);
} else {
    close(pipefd[1]);  /* Fecha escrita */
    char buf[16];
    read(pipefd[0], buf, sizeof(buf));
    close(pipefd[0]);
}

Memória compartilhada POSIX com shm_open e mmap é portável entre sistemas modernos:

#include <sys/mman.h>
#include <fcntl.h>

int fd = shm_open("/meu_shm", O_CREAT | O_RDWR, 0666);
ftruncate(fd, 4096);
int* dados = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
*dados = 42;

Semáforos POSIX (sem_open, sem_wait, sem_post) oferecem sincronização portável entre processos.

6. Controle de terminal e ambiente

Para manipular terminais, POSIX define tcgetattr e tcsetattr:

#include <termios.h>

struct termios old, new;
tcgetattr(STDIN_FILENO, &old);
new = old;
new.c_lflag &= ~(ICANON | ECHO);  /* Modo raw */
tcsetattr(STDIN_FILENO, TCSANOW, &new);
/* Lê caracteres sem esperar Enter */
tcsetattr(STDIN_FILENO, TCSANOW, &old);  /* Restaura */

Variáveis de ambiente são acessadas com getenv e setenv:

char* path = getenv("PATH");
setenv("MINHA_VAR", "valor", 1);  /* 1 = sobrescreve se existir */

7. Detectando e lidando com diferenças entre sistemas Unix

Nem todos os sistemas Unix implementam POSIX da mesma forma. Use macros de pré-processador para adaptar seu código:

/* Define explicitamente qual padrão usar */
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE   700

#if defined(__linux__)
    /* Específico Linux */
    #include <sys/epoll.h>
#elif defined(__APPLE__)
    /* Alternativa macOS para epoll: kqueue */
    #include <sys/event.h>
#else
    /* Fallback genérico POSIX */
    #include <poll.h>
#endif

Para testar recursos em tempo de execução:

long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
long maxpath = pathconf("/", _PC_PATH_MAX);

Funções como getline (GNU) não são POSIX; prefira fgets ou implemente sua própria leitura de linha.

8. Boas práticas para escrever código C portável entre Unix

  1. Use feature test macros explicitamente: Sempre defina _POSIX_C_SOURCE=200809L ou _XOPEN_SOURCE=700 antes de qualquer include. Isso evita surpresas com extensões GNU.

  2. Prefira tipos de tamanho fixo: Use int32_t, uint64_t de <stdint.h> em vez de int ou long. Nunca assuma que int tem 32 bits ou que long tem 64 bits.

  3. Compile com flags de portabilidade:

gcc -std=c99 -pedantic -D_POSIX_C_SOURCE=200809L -Wall -Wextra programa.c
  1. Evite extensões não POSIX: strdup é POSIX? Sim, desde POSIX.1-2008. asprintf? Não, é GNU. Prefira snprintf com buffer alocado.

  2. Use getaddrinfo em vez de gethostbyname: A primeira é POSIX e thread-safe; a segunda é obsoleta.

  3. Teste em múltiplos sistemas: Pelo menos Linux e macOS (que é certificado POSIX). Considere FreeBSD ou OpenBSD para teste adicional.

  4. Documente suposições: Se seu código depende de O_CLOEXEC (definido desde POSIX.1-2001), documente isso.

Seguindo estas práticas, seu código C rodará em qualquer sistema Unix-like com mudanças mínimas — ou nenhuma. POSIX não é apenas um padrão; é a garantia de que seu investimento em código sobreviverá a mudanças de sistema operacional.


Referências