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
-
Use feature test macros explicitamente: Sempre defina
_POSIX_C_SOURCE=200809Lou_XOPEN_SOURCE=700antes de qualquer include. Isso evita surpresas com extensões GNU. -
Prefira tipos de tamanho fixo: Use
int32_t,uint64_tde<stdint.h>em vez deintoulong. Nunca assuma queinttem 32 bits ou quelongtem 64 bits. -
Compile com flags de portabilidade:
gcc -std=c99 -pedantic -D_POSIX_C_SOURCE=200809L -Wall -Wextra programa.c
-
Evite extensões não POSIX:
strdupé POSIX? Sim, desde POSIX.1-2008.asprintf? Não, é GNU. Prefirasnprintfcom buffer alocado. -
Use
getaddrinfoem vez degethostbyname: A primeira é POSIX e thread-safe; a segunda é obsoleta. -
Teste em múltiplos sistemas: Pelo menos Linux e macOS (que é certificado POSIX). Considere FreeBSD ou OpenBSD para teste adicional.
-
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
- The Open Group Base Specifications Issue 7 (POSIX.1-2017) — Documentação oficial completa do padrão POSIX, incluindo todas as funções e headers para C.
- POSIX Threads Programming (LLNL) — Tutorial abrangente sobre pthreads com exemplos práticos em C, mantido pelo Lawrence Livermore National Laboratory.
- Unix Programming and Regular Expressions (GNU C Library Manual) — Seções sobre POSIX, E/S de baixo nível, processos e IPC na documentação oficial da glibc.
- Beej's Guide to Unix IPC — Guia prático e acessível sobre pipes, filas de mensagens, semáforos e memória compartilhada POSIX em C.
- Porting from Linux to macOS: POSIX Differences — Documentação da Apple sobre diferenças de implementação POSIX entre Linux e macOS, com exemplos de código adaptável.
- POSIX and C Standard Library Comparison (IBM) — Tabela comparativa detalhada entre funções ISO C e suas equivalentes POSIX, útil para entender quando usar cada uma.