Pular para o conteúdo principal

Aula 04 — System Calls: Arquivos, Diretórios e Informações

:::note Ambiente sugerido Os exemplos desta aula foram testados em Linux (Ubuntu 22.04 / Debian 12) com gcc instalado. Todos os snippets são executáveis — compile e execute para visualizar os conceitos. :::

Objetivos

Ao final desta aula você deve ser capaz de:

  • Classificar chamadas de sistema nas seis categorias do Silberschatz (§2.4).
  • Explicar o papel do descritor de arquivo (fd) como abstração do kernel sobre recursos de I/O.
  • Usar as syscalls de arquivo open(), read(), write(), close() e lseek() em programas C com tratamento de erro via errno/perror().
  • Consultar metadados de arquivo com stat() / fstat() e interpretar os campos da struct stat.
  • Navegar em diretórios programaticamente com opendir(), readdir() e closedir().
  • Obter informações de processo com getpid(), getppid(), getuid() e getcwd().
  • Diferenciar API de alto nível (fopen/printf) de syscall direta (open/write), e explicar quando usar cada uma.

Conteúdo

As seis categorias de chamadas de sistema

Toda interação de um programa com recursos do SO passa por chamadas de sistema. O Silberschatz (§2.4) agrupa-as em seis categorias:

CategoriaFinalidadeExemplos POSIX
Controle de processosCriar, encerrar, aguardar processosfork(), exec(), wait(), exit()
Gerenciamento de arquivosCriar, abrir, ler, escrever, fecharopen(), read(), write(), close()
Gerenciamento de dispositivosSolicitar/liberar/ler/escrever dispositivosioctl(), read(), write()
Manutenção de informaçõesObter/definir hora, dados do sistema/processogetpid(), time(), stat(), getcwd()
ComunicaçõesIPC: pipes, memória compartilhada, socketspipe(), shm_open(), socket()
ProteçãoPermissões de acesso a recursoschmod(), chown(), umask()

Diferença API vs. syscall: printf() é uma função da biblioteca C (API) — ela formata a string e internamente chama a syscall write(). fopen() é API — chama open(). A API fornece portabilidade e conveniência; a syscall é a fronteira real entre modo usuário e modo kernel.


O descritor de arquivo (fd)

Toda operação de I/O no UNIX usa um file descriptor — um inteiro não-negativo que o kernel associa a um recurso aberto (arquivo, dispositivo, pipe, socket).

Quando open() é chamado com sucesso, o kernel:

  1. Localiza ou cria a entrada do arquivo na tabela global de arquivos abertos.
  2. Aloca o menor fd disponível (≥ 3) na tabela do processo.
  3. Retorna esse fd ao chamador.

Se open() falha, retorna -1 e seta errno com o código do erro.


Syscalls de arquivo: open, read, write, close, lseek

open()

/* Somente leitura — assinatura da syscall open() */
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);
/* mode só é usado quando O_CREAT está presente */

Principais flags (combinadas com |):

FlagSignificado
O_RDONLYAbre somente para leitura
O_WRONLYAbre somente para escrita
O_RDWRAbre para leitura e escrita
O_CREATCria o arquivo se não existir (requer mode)
O_TRUNCTrunca o arquivo para tamanho 0 se existir
O_APPENDEscritas sempre vão para o final
O_EXCLCom O_CREAT, falha se o arquivo já existir

Programa completo: copiar arquivo via syscalls

/* Executável — compile: gcc -o copia copia.c
Uso: ./copia origem destino */
#include <fcntl.h> /* open(), flags O_* */
#include <unistd.h> /* read(), write(), close() */
#include <sys/stat.h> /* mode_t, S_IRUSR etc. */
#include <stdio.h> /* perror() */
#include <stdlib.h> /* exit() */

#define BUF_SIZE 4096 /* tamanho do buffer de cópia */

int main(int argc, char *argv[])
{
int fd_in, fd_out;
ssize_t n_lidos, n_escritos;
char buf[BUF_SIZE];

/* --- validação de argumentos --- */
if (argc != 3) {
fprintf(stderr, "Uso: %s <origem> <destino>\n", argv[0]);
exit(1);
}

/* --- abre o arquivo de origem para leitura --- */
fd_in = open(argv[1], O_RDONLY);
if (fd_in == -1) {
perror("open origem"); /* imprime msg de erro + descrição do errno */
exit(1);
}

/* --- abre/cria o destino para escrita, permissão rw-r--r-- --- */
fd_out = open(argv[2],
O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); /* 0644 */
if (fd_out == -1) {
perror("open destino");
close(fd_in); /* sempre feche fds abertos antes de sair */
exit(1);
}

/* --- loop de cópia: lê blocos de BUF_SIZE bytes --- */
while ((n_lidos = read(fd_in, buf, BUF_SIZE)) > 0) {
/* read() retorna: bytes lidos (>0), EOF (0) ou erro (-1) */
n_escritos = write(fd_out, buf, n_lidos);
if (n_escritos != n_lidos) {
perror("write");
close(fd_in);
close(fd_out);
exit(1);
}
}

if (n_lidos == -1) { /* distingue erro de EOF */
perror("read");
}

/* --- fecha os dois descritores --- */
close(fd_in);
close(fd_out);
return 0;
}

Pontos de atenção no código:

  • read() pode retornar menos bytes do que solicitado (e.g., pipe, socket, arquivo pequeno) — o loop garante a cópia completa.
  • write() também pode escrever parcialmente — produção robusta verificaria e chamaria write() novamente.
  • Sempre feche os fds, mesmo em caso de erro — evita vazamento de descritores.
  • perror() imprime a mensagem do errno automaticamente — use sempre ao tratar erros de syscall.

lseek() — reposicionamento

/* Somente leitura — assinatura */
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
/* whence: SEEK_SET (início), SEEK_CUR (posição atual), SEEK_END (fim) */
/* Executável — exemplos de lseek */
lseek(fd, 0, SEEK_SET); /* vai para o início do arquivo */
lseek(fd, 0, SEEK_END); /* vai para o final (próximo a tamanho) */
off_t pos = lseek(fd, 0, SEEK_CUR); /* obtém a posição atual */
lseek(fd, -10, SEEK_CUR); /* volta 10 bytes da posição atual */

Metadados de arquivo: stat() e fstat()

stat() retorna a estrutura struct stat com todos os metadados de um arquivo sem precisar abri-lo.

/* Executável — exibe metadados de um arquivo */
/* Compile: gcc -o metadados metadados.c */
#include <sys/stat.h> /* stat(), struct stat, S_IS* macros */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h> /* ctime() */

int main(int argc, char *argv[])
{
struct stat sb; /* estrutura que o kernel preenche */

if (argc != 2) { fprintf(stderr, "Uso: %s <arquivo>\n", argv[0]); exit(1); }

/* stat() recebe pathname e preenche sb sem abrir o arquivo */
if (stat(argv[1], &sb) == -1) {
perror("stat");
exit(1);
}

printf("Arquivo: %s\n", argv[1]);
printf("Inode: %ld\n", (long)sb.st_ino);
printf("Tamanho: %ld bytes\n", (long)sb.st_size);
printf("Blocos: %ld\n", (long)sb.st_blocks);
printf("Permissões: %o (octal)\n", sb.st_mode & 0777);
printf("Links: %ld\n", (long)sb.st_nlink);
printf("UID/GID: %d / %d\n", sb.st_uid, sb.st_gid);
printf("Último acesso: %s", ctime(&sb.st_atime));
printf("Última modif.: %s", ctime(&sb.st_mtime));

/* macros S_IS* identificam o tipo do arquivo */
if (S_ISREG(sb.st_mode)) puts("Tipo: arquivo regular");
else if (S_ISDIR(sb.st_mode)) puts("Tipo: diretório");
else if (S_ISLNK(sb.st_mode)) puts("Tipo: link simbólico");
else if (S_ISCHR(sb.st_mode)) puts("Tipo: dispositivo de caractere");
else if (S_ISBLK(sb.st_mode)) puts("Tipo: dispositivo de bloco");

return 0;
}

Principais campos da struct stat:

CampoTipoConteúdo
st_inoino_tNúmero do inode
st_modemode_tTipo e permissões
st_nlinknlink_tNúmero de hard links
st_uid / st_giduid_t / gid_tDono e grupo
st_sizeoff_tTamanho em bytes
st_blksizeblksize_tTamanho preferido de bloco
st_blocksblkcnt_tBlocos de 512B alocados
st_atimetime_tÚltimo acesso
st_mtimetime_tÚltima modificação de conteúdo
st_ctimetime_tÚltima alteração de metadados

/* Executável — lista entradas de um diretório */
/* Compile: gcc -o lista lista.c */
#include <dirent.h> /* opendir, readdir, closedir, struct dirent */
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
DIR *dir; /* handle do diretório */
struct dirent *entrada; /* cada entrada lida por readdir() */
struct stat sb;
char caminho[512];
const char *dirpath = (argc > 1) ? argv[1] : ".";

dir = opendir(dirpath); /* abre o diretório como stream */
if (dir == NULL) {
perror("opendir");
exit(1);
}

printf("%-30s %-10s %s\n", "Nome", "Tipo", "Tamanho");
printf("%-30s %-10s %s\n", "----", "----", "-------");

/* readdir() retorna NULL ao final ou em erro */
while ((entrada = readdir(dir)) != NULL) {
/* monta o caminho completo para o stat() */
snprintf(caminho, sizeof(caminho), "%s/%s", dirpath, entrada->d_name);
stat(caminho, &sb);

const char *tipo = S_ISDIR(sb.st_mode) ? "diretório" :
S_ISLNK(sb.st_mode) ? "link" :
S_ISREG(sb.st_mode) ? "arquivo" : "outro";

printf("%-30s %-10s %ld\n",
entrada->d_name, tipo, (long)sb.st_size);
}

closedir(dir); /* libera o handle do diretório */
return 0;
}

Atividade prática rápida: execute ./lista /proc/self para ver os "arquivos" que o kernel expõe sobre o próprio processo em execução — fd/, maps, status, cmdline etc.


Informações de processo: getpid, getppid, getuid, getcwd

/* Executável — informações do processo atual */
/* Compile: gcc -o info info.c */
#include <unistd.h> /* getpid, getppid, getuid, getcwd */
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
char cwd[512];

printf("PID (processo atual): %d\n", getpid());
printf("PPID (processo pai): %d\n", getppid());
printf("UID (usuário efetivo): %d\n", getuid());
printf("EUID (usuário real): %d\n", geteuid());
printf("GID (grupo): %d\n", getgid());

if (getcwd(cwd, sizeof(cwd)) != NULL)
printf("CWD (diretório atual): %s\n", cwd);
else
perror("getcwd");

return 0;
}

Tabela das principais syscalls de informação:

SyscallRetornoHeader
getpid()PID do processo atual<unistd.h>
getppid()PID do processo pai<unistd.h>
getuid()UID real do usuário<unistd.h>
geteuid()UID efetivo (para permissões)<unistd.h>
getgid()GID real<unistd.h>
getcwd()Diretório de trabalho atual<unistd.h>
time()Tempo Unix (segundos desde 1970)<time.h>
uname()Nome e versão do SO<sys/utsname.h>

Como o kernel recebe uma syscall: strace

strace é um utilitário Linux que intercepta e exibe todas as syscalls realizadas por um processo. É a melhor ferramenta para visualizar a ponte entre código C e o kernel.

# Rastreia todas as syscalls de ./copia
strace ./copia origem.txt destino.txt

# Filtra apenas as syscalls de arquivo
strace -e trace=file ./copia origem.txt destino.txt

# Rastreia um processo em execução pelo PID
strace -p 1234

Atividade prática rápida: execute strace ls e observe as syscalls openat(), getdents64() e write() que o comando ls faz internamente. Identifique os descritores de arquivo usados.


Exercícios

Questões dissertativas

Q1

Explique o conceito de descritor de arquivo (file descriptor). Por que o kernel usa um inteiro para representar um arquivo aberto, em vez de um ponteiro direto para o arquivo em disco?

Q2

Qual a diferença entre as chamadas de alto nível fopen()/fread()/fwrite() da libc e as syscalls diretas open()/read()/write()? Em que situação cada uma deve ser preferida?

Q3

Analise o seguinte trecho de código C. Identifique todos os erros e problemas, e escreva a versão corrigida: fd = open('dados.txt', O_RDONLY); buf = read(fd, buffer, 100); write(1, buffer, buf); close(fd);

Q4

Explique o que a syscall stat() retorna e quais informações ela fornece. Como ela difere de fstat()? Dê um exemplo de uso prático de stat() em um programa.

Q5

Um programa abre um arquivo com open(), lê 100 bytes com read(), e então chama lseek(fd, 0, SEEK_SET). O que acontece? Se depois chamar read() novamente com 100 bytes, o que será lido?

Q6Difícil

Escreva um programa C que, dado um nome de arquivo como argumento, exiba se ele é um arquivo regular, diretório ou link simbólico, e mostre seu tamanho em bytes e as permissões em formato octal.

Q7

O que acontece quando um processo chama close() em um fd que já foi fechado anteriormente? E o que acontece com os dados escritos se close() não for chamado antes do processo terminar?

Q8

Escreva um programa C que liste os nomes de todos os arquivos regulares (não diretórios, não links) de um diretório passado como argumento, junto com seu tamanho em bytes.

Q9

Por que os fds 0, 1 e 2 são especiais e como o shell usa essa convenção para implementar redirecionamento (ex.: './programa > saida.txt')?

Q10Difícil

Explique o que strace faz e qual syscall subjacente é invocada quando executamos printf('hello\ ') em C. Trace todo o caminho desde a chamada na aplicação até o hardware.


Quiz de múltipla escolha

Quiz10 questões

1. Qual o primeiro file descriptor disponível após a abertura de um processo, considerando que stdin (0), stdout (1) e stderr (2) já estão abertos?

  • a)0 — o kernel sempre reutiliza o menor fd disponível desde 0
  • b)1 — pois stderr ainda não foi aberto
  • c)3 — o menor inteiro não-negativo ainda não usado
  • d)4 — por convenção POSIX
  • e)Depende do sistema operacional

2. Uma chamada read(fd, buf, 512) retornou 200. O que isso significa?

  • a)Ocorreu um erro — read() deveria retornar 512 ou -1
  • b)O arquivo tem exatamente 200 bytes
  • c)Foram lidos 200 bytes; pode ser EOF, final de bloco ou final de pipe
  • d)O buffer buf tem apenas 200 bytes disponíveis
  • e)O kernel leu 200 bytes mas vai completar os outros 312 na próxima chamada automaticamente

3. Qual flag de open() garante que o arquivo será CRIADO se não existir, mas FALHA se ele já existir (para evitar sobrescrever acidentalmente)?

  • a)O_CREAT apenas
  • b)O_TRUNC | O_WRONLY
  • c)O_CREAT | O_EXCL
  • d)O_CREAT | O_TRUNC
  • e)O_RDWR | O_CREAT

4. Qual syscall é invocada internamente quando um programa C chama printf()?

  • a)print() — específica para output formatado
  • b)write() — com o fd 1 (stdout)
  • c)send() — específica para I/O de terminal
  • d)putchar() — syscall de saída de caractere
  • e)fprintf() — versão de syscall do printf

5. O que stat() e fstat() têm em comum, e qual é a diferença entre elas?

  • a)Ambas recebem um pathname; stat() é mais rápida
  • b)Ambas preenchem struct stat com metadados; stat() recebe pathname, fstat() recebe um fd aberto
  • c)fstat() é obsoleta — stat() substituiu ambas no POSIX moderno
  • d)stat() retorna só o tamanho; fstat() retorna todos os metadados
  • e)Ambas são idênticas mas stat() só funciona com arquivos regulares

6. Um processo chama lseek(fd, 0, SEEK_END) em um arquivo de 1000 bytes. Qual é o valor retornado e o que acontece se uma write() for executada imediatamente após?

  • a)Retorna 0; write() escreve no início do arquivo
  • b)Retorna 1000; write() anexa dados após o byte 999 (no final do arquivo)
  • c)Retorna -1; lseek() não pode ir além do último byte
  • d)Retorna 999; write() sobrescreve o último byte
  • e)Retorna 1000; write() falha pois já está no limite do arquivo

7. Qual syscall é usada para iterar sobre as entradas de um diretório em C, e o que ela retorna quando chega ao final?

  • a)readdir() — retorna NULL ao final (ou em erro)
  • b)read() — retorna 0 ao final do diretório
  • c)scandir() — retorna -1 quando não há mais entradas
  • d)getdents() — retorna uma string vazia ao final
  • e)opendir() — é ela que itera e retorna NULL ao final

8. O que acontece se um processo tenta usar um file descriptor inválido (ex.: fd = -1) em uma chamada read()?

  • a)O kernel cria um arquivo temporário e usa-o
  • b)O processo é encerrado imediatamente com SIGKILL
  • c)read() retorna -1 e errno é definido como EBADF (Bad file descriptor)
  • d)read() retorna 0, indicando que não há dados
  • e)O comportamento é indefinido e pode corromper o processo

9. Qual é a syscall POSIX para obter o PID do processo atual, e qual para obter o PID do processo pai?

  • a)pid() para o atual; ppid() para o pai
  • b)getpid() para o atual; getppid() para o pai
  • c)getpid() para o atual; getparentpid() para o pai
  • d)self() para o atual; parent() para o pai
  • e)getpid() retorna ambos num struct

10. O que o utilitário strace faz e qual syscall ele usa internamente para interceptar as chamadas do processo monitorado?

  • a)strace usa SIGTRAP para pausar o processo a cada instrução
  • b)strace usa ptrace() — uma syscall de depuração que permite inspecionar outro processo
  • c)strace usa /proc/PID/syscall para ler chamadas já executadas
  • d)strace usa valgrind internamente para interceptar chamadas
  • e)strace é implementado no kernel e não precisa de syscall especial

Referências

Principais (essenciais)

  • SILBERSCHATZ, A.; GALVIN, P. B.; GAGNE, G. Fundamentos de Sistemas Operacionais. 9. ed. LTC, 2015.
    • Seções: 2.2, 2.3, 2.4 (todos os tipos), 2.5, 3.3
  • man 2 open, man 2 read, man 2 write, man 2 close, man 2 stat, man 3 opendir — páginas de manual do Linux (referência completa de cada syscall)

Aprofundamento (opcionais)

  • KERRISK, M. The Linux Programming Interface. No Starch Press, 2010. Cap. 4–5 (I/O de arquivo), Cap. 15 (atributos de arquivo), Cap. 18 (diretórios).
  • STEVENS, W. R.; RAGO, S. A. Advanced Programming in the UNIX Environment. 3. ed. Addison-Wesley, 2013. Cap. 3.
  • Linux kernel source — fs/open.c (implementação de open()): elixir.bootlin.com

Recursos Complementares

Opcional — Vídeos selecionados para reforçar os conceitos desta aula.

Thumbnail do vídeo: Operating System Basics12:06
ENOpcional

Operating System Basics

Neso Academy

Revisão de fundamentos de operação do SO e fronteira usuário/kernel, contexto importante para entender syscalls na prática.

Thumbnail do vídeo: Operating Systems: Crash Course Computer Science #1811:43
ENOpcional

Operating Systems: Crash Course Computer Science #18

CrashCourse

Complemento introdutório sobre serviços do SO e interface com aplicações, útil antes dos exemplos de open/read/write/stat.

Thumbnail do vídeo: Introduction to Operating System | Full Course for Beginners2:11:13
ENAprofundamento

Introduction to Operating System | Full Course for Beginners

Mike Murphy Co

Aprofundamento com visão ampla de chamadas de sistema, processos e gerenciamento de recursos.

Thumbnail do vídeo: Operating Systems — Full Course (25h)25:00:00
ENAprofundamento

Operating Systems — Full Course (25h)

freeCodeCamp.org

Para esta aula, localize os módulos sobre System Calls e Types of System Calls dentro do curso completo.