MODELLO DI PROCESSO UNIX
Struttura di un Processo

Ogni processo UNIX ha: PID, PPID, UID/GID, tabella file descriptor, spazio di indirizzamento virtuale, segnali pendenti, priority/nice, environment.

0xFFFF...FFFF
KERNEL SPACEMappato nello spazio virtuale ma inaccessibile da user mode. Syscall tramite trap.
0x7FFF...FFFF
STACKCresce verso il basso (↓). Variabili locali, indirizzi di ritorno, argomenti, environ.
↓ ↓ ↓
... spazio libero (stack cresce verso heap) ...
↑ ↑ ↑
HEAPCresce verso l'alto (↑). Allocazione dinamica (malloc/brk/mmap).
BSSVariabili globali/statiche non inizializzate (azzerato a runtime).
DATAVariabili globali inizializzate, stringhe costanti (.data / .rodata).
0x0040 0000
TEXTCodice eseguibile (read-only). Condivisibile tra processi dopo fork (COW).
0x0000 0000
NULL page (unmapped, accesso = SIGSEGV)
Header Principali
HeaderFunzionalità
<unistd.h>fork, exec, pipe, dup, read, write, close, getpid, ...
<sys/types.h>pid_t, uid_t, gid_t, off_t, size_t, key_t, ...
<sys/wait.h>wait, waitpid, macro WIFEXITED, WEXITSTATUS, ...
<signal.h>signal, sigaction, kill, sigprocmask, sigset_t, ...
<fcntl.h>open, fcntl, flag O_RDONLY, O_CREAT, ...
<sys/stat.h>mkfifo, stat, fstat, chmod, mode_t, ...
<sys/ipc.h>ftok, IPC_CREAT, IPC_RMID, IPC_EXCL, ...
<sys/shm.h>shmget, shmat, shmdt, shmctl (System V)
<sys/sem.h>semget, semop, semctl (System V)
<sys/msg.h>msgget, msgsnd, msgrcv, msgctl (System V)
<sys/mman.h>mmap, munmap, shm_open, shm_unlink (POSIX)
<semaphore.h>sem_open, sem_wait, sem_post (POSIX)
<mqueue.h>mq_open, mq_send, mq_receive (POSIX)
<sys/socket.h>socket, bind, listen, accept, connect, ...
<sys/un.h>struct sockaddr_un (Unix domain socket)
Identificativi di Processo
#include <unistd.h>
#include <sys/types.h>

pid_t getpid(void);    // PID del processo corrente
pid_t getppid(void);   // PID del padre
uid_t getuid(void);    // UID reale
uid_t geteuid(void);   // UID effettivo
gid_t getgid(void);    // GID reale
gid_t getegid(void);   // GID effettivo
pid_t getpgrp(void);   // Process Group ID
pid_t getsid(pid_t pid); // Session ID
fork()
Semantica

fork() crea una copia esatta del processo chiamante. Il figlio riceve una copia dello spazio di indirizzamento (copy-on-write), della tabella dei file descriptor, dei gestori di segnale e dell'ambiente.

#include <unistd.h>

pid_t fork(void);
// Ritorna:
//   nel padre  → PID del figlio (> 0)
//   nel figlio → 0
//   errore     → -1 (errno settato)
Esempio Base
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        /* ─── PROCESSO FIGLIO ─── */
        printf("Figlio: PID=%d, PPID=%d\n", getpid(), getppid());
        exit(EXIT_SUCCESS);
    }

    /* ─── PROCESSO PADRE ─── */
    printf("Padre: PID=%d, figlio=%d\n", getpid(), pid);

    int status;
    waitpid(pid, &status, 0);

    if (WIFEXITED(status))
        printf("Figlio uscito con codice %d\n", WEXITSTATUS(status));

    return 0;
}
Cosa Eredita il Figlio

Ereditato (copia):

• Spazio di indirizzamento (COW)
• Tabella file descriptor aperti
• UID/GID reale ed effettivo
• Process group, session ID
• Ambiente (environ)
• Gestori di segnale
• Maschera segnali (signal mask)
• umask, directory corrente, root dir
• Limiti risorse (rlimit)

NON ereditato / diverso:

• PID (nuovo, unico)
• PPID (= PID del padre)
• Segnali pendenti (azzerati)
• Lock sui file (non ereditati)
• Timer (alarm azzerato)
• Contatori CPU (azzerati)
• File lock POSIX (non ereditati)

Fork Multiplo
/* Creare N processi figli */
#define N_CHILDREN 5

for (int i = 0; i < N_CHILDREN; i++) {
    pid_t pid = fork();
    if (pid == 0) {
        /* Codice del figlio i-esimo */
        printf("Figlio %d (PID %d)\n", i, getpid());
        exit(i);  // ogni figlio esce subito
    }
    if (pid < 0) { perror("fork"); exit(1); }
}

/* Padre attende tutti i figli */
for (int i = 0; i < N_CHILDREN; i++) {
    int status;
    pid_t w = wait(&status);
    printf("Figlio %d terminato\n", w);
}
Attenzione: senza l'exit() nel figlio, ogni figlio continuerebbe il loop e creerebbe a sua volta altri figli (fork bomb).
vfork()

vfork() crea un processo figlio che condivide lo spazio di indirizzamento del padre. Il padre è sospeso finché il figlio non chiama exec() o _exit(). Da usare solo se seguita immediatamente da exec(). In pratica, su sistemi moderni con COW, fork() è quasi altrettanto efficiente.

pid_t pid = vfork();
if (pid == 0) {
    execl("/bin/ls", "ls", "-la", (char *)NULL);
    _exit(127);  // MAI usare exit() dopo vfork, solo _exit
}
exec() — Famiglia di Funzioni
Varianti exec

Le funzioni exec sostituiscono l'immagine del processo corrente con un nuovo programma. Non ritornano in caso di successo.

FunzionePath/NameArgomentiAmbiente
execl()PathnameLista (variadic)Ereditato
execlp()Cerca in PATHLista (variadic)Ereditato
execle()PathnameLista (variadic)Esplicito (envp)
execv()PathnameArray argv[]Ereditato
execvp()Cerca in PATHArray argv[]Ereditato
execvpe()Cerca in PATHArray argv[]Esplicito (envp)
execve()PathnameArray argv[]Esplicito (envp)
Mnemonico: l=list, v=vector, p=PATH, e=environment. execve() è la syscall reale, le altre sono wrapper libc.
Esempi
/* execl — lista argomenti terminata da NULL */
execl("/bin/ls", "ls", "-l", "/tmp", (char *)NULL);

/* execlp — cerca in PATH */
execlp("gcc", "gcc", "-o", "prog", "main.c", (char *)NULL);

/* execv — array di argomenti */
char *argv[] = {"ls", "-la", "/home", NULL};
execv("/bin/ls", argv);

/* execve — con ambiente esplicito */
char *envp[] = {"HOME=/tmp", "PATH=/bin:/usr/bin", NULL};
execve("/bin/env", (char *[]){"env", NULL}, envp);
Pattern fork + exec
pid_t pid = fork();
if (pid == 0) {
    /* Nel figlio: esegui un altro programma */
    execlp("sort", "sort", "data.txt", (char *)NULL);
    /* Se exec ritorna, c'è stato un errore */
    perror("execlp");
    _exit(127);
}
/* Padre continua... */
waitpid(pid, NULL, 0);
Dopo fork(), nel figlio usare _exit() (non exit()) per evitare il flush dei buffer stdio ereditati dal padre.
Cosa Preserva exec

PID, PPID, UID/GID, session/process group, fd aperti (senza FD_CLOEXEC), segnali con azione default/ignore, signal mask, umask, cwd, rlimit.

Cosa resetta: gestori segnale custom → SIG_DFL, memory mapping, memory lock, pending signals, timer.

wait() & waitpid()
Prototipi
#include <sys/wait.h>

pid_t wait(int *status);
// Blocca finché un qualsiasi figlio termina
// Ritorna PID del figlio, -1 se errore (o nessun figlio)

pid_t waitpid(pid_t pid, int *status, int options);
// pid > 0  : attende il figlio con quel PID
// pid == -1: attende un qualsiasi figlio (come wait)
// pid == 0 : attende un figlio nel proprio group
// pid < -1 : attende un figlio nel group |pid|
Opzioni waitpid
FlagDescrizione
0Bloccante (attende terminazione)
WNOHANGNon bloccante: ritorna 0 se nessun figlio terminato
WUNTRACEDRitorna anche per figli stoppati (non solo terminati)
WCONTINUEDRitorna anche per figli riavviati con SIGCONT
Macro di Analisi Status
MacroDescrizione
WIFEXITED(s)Vero se terminato normalmente (exit/return)
WEXITSTATUS(s)Codice di uscita (solo se WIFEXITED)
WIFSIGNALED(s)Vero se terminato da un segnale
WTERMSIG(s)Numero segnale (solo se WIFSIGNALED)
WCOREDUMP(s)Vero se ha generato core dump
WIFSTOPPED(s)Vero se stoppato (WUNTRACED)
WSTOPSIG(s)Segnale di stop (solo se WIFSTOPPED)
WIFCONTINUED(s)Vero se riavviato con SIGCONT
Esempio Completo
int status;
pid_t w = waitpid(pid, &status, 0);

if (w == -1) {
    perror("waitpid");
} else if (WIFEXITED(status)) {
    printf("Exit code: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
    printf("Killed by signal %d%s\n",
           WTERMSIG(status),
           WCOREDUMP(status) ? " (core dumped)" : "");
} else if (WIFSTOPPED(status)) {
    printf("Stopped by signal %d\n", WSTOPSIG(status));
}
Processo Zombie e Orfano

Zombie: processo terminato ma il padre non ha ancora chiamato wait(). Occupa un entry nella tabella dei processi (PID, exit status). Soluzione: il padre deve chiamare wait() o installare SIGCHLD con SA_NOCLDWAIT.

Orfano: processo il cui padre è terminato. Viene adottato da init (PID 1) che chiama wait() automaticamente.

/* Evitare zombie: ignorare SIGCHLD */
struct sigaction sa = {
    .sa_handler = SIG_IGN,
    .sa_flags   = SA_NOCLDWAIT
};
sigaction(SIGCHLD, &sa, NULL);

/* Oppure: double-fork per evitare zombie */
pid_t pid = fork();
if (pid == 0) {
    pid_t pid2 = fork();
    if (pid2 == 0) {
        /* Nipote: il vero worker. Sarà orfano → adottato da init */
        execl("/usr/bin/worker", "worker", (char*)NULL);
        _exit(127);
    }
    _exit(0);  // primo figlio esce subito
}
waitpid(pid, NULL, 0);  // attende solo il primo figlio
exit() & _exit()
Differenze
FunzioneHeaderFlush stdioatexit()Uso
exit(status)<stdlib.h>EsegueTerminazione normale
_exit(status)<unistd.h>NoNoDopo fork(), in caso di errore exec
_Exit(status)<stdlib.h>NoNoEquivalente C99 di _exit
atexit() — Cleanup Handler
#include <stdlib.h>

void cleanup1(void) { printf("cleanup 1\n"); }
void cleanup2(void) { printf("cleanup 2\n"); }

int main(void) {
    atexit(cleanup1);  // registrate in ordine LIFO
    atexit(cleanup2);  // cleanup2 eseguita per prima
    exit(EXIT_SUCCESS);
    // Output: "cleanup 2" poi "cleanup 1"
}
GRUPPI DI PROCESSI & SESSIONI
Concetti

Process Group: insieme di processi correlati (es. pipeline shell). Il leader ha PGID == PID. I segnali possono essere inviati a tutto il gruppo.

Session: insieme di process group. Il session leader è il processo che ha chiamato setsid(). Ogni sessione può avere un terminale di controllo.

/* Gerarchia: Session → Process Group → Process */

  Session (SID=100)
  ├── Foreground group (PGID=100)
  │   └── bash (PID=100, leader)
  ├── Background group (PGID=200)
  │   ├── find (PID=200, leader)
  │   └── grep (PID=201)
  └── Background group (PGID=300)
      └── daemon (PID=300, leader)
Funzioni
int   setpgid(pid_t pid, pid_t pgid);  // Imposta PGID
pid_t getpgrp(void);                  // Ottiene PGID corrente
pid_t setsid(void);                   // Crea nuova sessione
pid_t getsid(pid_t pid);              // Ottiene SID
pid_t tcgetpgrp(int fd);              // Foreground group del terminale
int   tcsetpgrp(int fd, pid_t pgrp);  // Imposta foreground group

/* Inviare segnale a tutto il group */
kill(-pgid, SIGTERM);  // PID negativo = intero group
killpg(pgid, SIGTERM); // equivalente
DAEMON
Creazione di un Daemon (Classica)
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>

void daemonize(void) {
    /* 1. Fork e termina il padre */
    pid_t pid = fork();
    if (pid < 0) exit(1);
    if (pid > 0) exit(0);  // padre esce

    /* 2. Nuova sessione (diventa session leader) */
    if (setsid() < 0) exit(1);

    /* 3. Secondo fork: non più session leader → nessun terminale */
    pid = fork();
    if (pid < 0) exit(1);
    if (pid > 0) exit(0);

    /* 4. Cambia directory e umask */
    chdir("/");
    umask(0);

    /* 5. Chiudi e redireziona fd standard */
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);
    open("/dev/null", O_RDONLY);  // stdin  → /dev/null (fd 0)
    open("/dev/null", O_WRONLY);  // stdout → /dev/null (fd 1)
    open("/dev/null", O_WRONLY);  // stderr → /dev/null (fd 2)
}
Su GNU/Linux, daemon(0, 0) (non-standard) esegue gli step 1-5 automaticamente.
FILE DESCRIPTOR
Concetti

Un file descriptor (fd) è un intero non negativo che identifica un file aperto nel kernel. Ogni processo ha una tabella di fd. I primi tre sono riservati:

fdCostanteStream CDescrizione
0STDIN_FILENOstdinStandard input
1STDOUT_FILENOstdoutStandard output
2STDERR_FILENOstderrStandard error
/* Struttura kernel (semplificata) */

  Per-process fd table        System open file table     Inode table
  ┌────┬──────────┐          ┌──────────────────┐        ┌───────────┐
  │ 0  │ ──────── │─────────>│ offset, flags    │──────> │ inode     │
  │ 1  │ ──────── │─────┐    │ ref count        │        │ type,size │
  │ 2  │ ──────── │──┐  │    └──────────────────┘        │ perms     │
  │ 3  │ ──────── │  │  └──> ┌──────────────────┐        └───────────┘
  │... │          │  │       │ offset, flags    │
  └────┴──────────┘  └─────> │ ref count        │
                             └──────────────────┘
open() & close()
Prototipi
#include <fcntl.h>
#include <sys/stat.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);  // = open(path, O_WRONLY|O_CREAT|O_TRUNC, mode)
int close(int fd);
Flag di open()
FlagDescrizione
O_RDONLYSola lettura
O_WRONLYSola scrittura
O_RDWRLettura e scrittura
O_CREATCrea il file se non esiste (richiede mode)
O_EXCLCon O_CREAT: fallisce se il file esiste (atomico)
O_TRUNCTronca a 0 byte se esiste
O_APPENDScrittura atomica in append
O_NONBLOCKI/O non bloccante
O_CLOEXECChiudi automaticamente su exec (atomico)
O_SYNCScrittura sincrona (flush su disco)
O_DIRECTORYFallisce se non è una directory
O_NOFOLLOWNon seguire symlink
Permessi (mode)
/* mode_t: combinazione OR di costanti */
int fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
//                                                         ^^^^
//  0644 = rw-r--r--
//  S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH  (equivalente)

/* Costanti simboliche */
S_IRWXU  0700   // user: rwx
S_IRUSR  0400   // user: r
S_IWUSR  0200   // user: w
S_IXUSR  0100   // user: x
S_IRWXG  0070   // group: rwx
S_IRWXO  0007   // other: rwx

/* Il mode effettivo è: mode & ~umask */
lseek()
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);
// whence: SEEK_SET (inizio), SEEK_CUR (corrente), SEEK_END (fine)
// Ritorna: nuova posizione, -1 se errore

off_t pos = lseek(fd, 0, SEEK_CUR);   // posizione corrente
off_t sz  = lseek(fd, 0, SEEK_END);   // dimensione file
lseek(fd, 100, SEEK_SET);               // posiziona a byte 100
read() & write()
Prototipi
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
// Ritorna: byte letti (0 = EOF), -1 se errore
// Può leggere MENO di count byte (short read)

ssize_t write(int fd, const void *buf, size_t count);
// Ritorna: byte scritti, -1 se errore
// Può scrivere MENO di count byte (short write)
Lettura/Scrittura Completa (Robusta)
/* Legge esattamente n byte (gestisce short read e EINTR) */
ssize_t read_all(int fd, void *buf, size_t n) {
    size_t total = 0;
    while (total < n) {
        ssize_t r = read(fd, (char*)buf + total, n - total);
        if (r < 0) {
            if (errno == EINTR) continue;  // interrotto da segnale
            return -1;
        }
        if (r == 0) break;  // EOF
        total += r;
    }
    return total;
}

/* Scrive esattamente n byte (gestisce short write e EINTR) */
ssize_t write_all(int fd, const void *buf, size_t n) {
    size_t total = 0;
    while (total < n) {
        ssize_t w = write(fd, (const char*)buf + total, n - total);
        if (w < 0) {
            if (errno == EINTR) continue;
            return -1;
        }
        total += w;
    }
    return total;
}
Scatter/Gather I/O
#include <sys/uio.h>

struct iovec iov[3];
iov[0].iov_base = header;  iov[0].iov_len = hdr_len;
iov[1].iov_base = data;    iov[1].iov_len = data_len;
iov[2].iov_base = trailer; iov[2].iov_len = trl_len;

ssize_t n = writev(fd, iov, 3);  // scrittura atomica di più buffer
ssize_t m = readv(fd, iov, 3);   // lettura distribuita su più buffer

/* pread/pwrite — lettura/scrittura a offset senza modificare posizione */
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
dup() & dup2()
Prototipi
#include <unistd.h>

int dup(int oldfd);
// Duplica oldfd sul più basso fd disponibile
// Ritorna: nuovo fd, -1 se errore

int dup2(int oldfd, int newfd);
// Duplica oldfd su newfd (chiude newfd se aperto)
// Atomico: close(newfd) + dup(oldfd) in una sola operazione
// Ritorna: newfd, -1 se errore

int dup3(int oldfd, int newfd, int flags);  // Linux: O_CLOEXEC
Redirezione I/O
/* Redirezionare stdout su un file */
int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
int saved_stdout = dup(STDOUT_FILENO);  // salva stdout
dup2(fd, STDOUT_FILENO);                 // stdout → file
close(fd);

printf("Questo va nel file\n");

/* Ripristinare stdout */
dup2(saved_stdout, STDOUT_FILENO);
close(saved_stdout);

printf("Questo va sul terminale\n");
Redirezione stderr su stdout
/* Equivalente shell: 2>&1 */
dup2(STDOUT_FILENO, STDERR_FILENO);

/* Equivalente shell: cmd > file 2>&1 */
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
fcntl()
Prototipi e Comandi
#include <fcntl.h>

int fcntl(int fd, int cmd, ... );
ComandoArgomentoDescrizione
F_DUPFDint minfdDuplica fd (≥ minfd)
F_DUPFD_CLOEXECint minfdDuplica con O_CLOEXEC
F_GETFDOttiene fd flags (FD_CLOEXEC)
F_SETFDint flagsImposta fd flags
F_GETFLOttiene file status flags
F_SETFLint flagsImposta file status flags (O_NONBLOCK, O_APPEND)
F_SETLKstruct flock*Lock non bloccante (file locking)
F_SETLKWstruct flock*Lock bloccante (attende)
F_GETLKstruct flock*Testa se un lock è possibile
Impostare Non-Blocking
/* Attivare O_NONBLOCK su un fd */
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

/* Disattivare O_NONBLOCK */
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
select() & poll() — I/O Multiplexing
select()
#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

/* Macro per manipolare fd_set */
FD_ZERO(&set);        // azzera il set
FD_SET(fd, &set);     // aggiunge fd al set
FD_CLR(fd, &set);     // rimuove fd dal set
FD_ISSET(fd, &set);   // testa se fd è nel set
/* Esempio: attende dati da stdin o da una pipe */
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
FD_SET(pipe_fd, &readfds);

int maxfd = (pipe_fd > STDIN_FILENO) ? pipe_fd : STDIN_FILENO;
struct timeval tv = { .tv_sec = 5, .tv_usec = 0 };

int ret = select(maxfd + 1, &readfds, NULL, NULL, &tv);
if (ret > 0) {
    if (FD_ISSET(STDIN_FILENO, &readfds))
        /* dati disponibili su stdin */;
    if (FD_ISSET(pipe_fd, &readfds))
        /* dati disponibili sulla pipe */;
} else if (ret == 0) {
    /* timeout */
} else {
    perror("select");
}
poll()
#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout_ms);

struct pollfd {
    int   fd;       // file descriptor
    short events;   // eventi richiesti
    short revents;  // eventi occorsi (output)
};

/* Eventi */
POLLIN    // dati in lettura disponibili
POLLOUT   // scrittura possibile
POLLERR   // errore (solo revents)
POLLHUP   // hang up (solo revents)
POLLNVAL  // fd non valido (solo revents)
/* Esempio: monitorare due fd */
struct pollfd fds[2] = {
    { .fd = STDIN_FILENO, .events = POLLIN },
    { .fd = pipe_fd,      .events = POLLIN },
};

int ret = poll(fds, 2, 5000);  // timeout 5 secondi
if (ret > 0) {
    if (fds[0].revents & POLLIN) { /* stdin pronto */ }
    if (fds[1].revents & POLLIN) { /* pipe pronta */ }
    if (fds[1].revents & POLLHUP) { /* pipe chiusa */ }
}
poll() è preferibile a select(): nessun limite FD_SETSIZE, non modifica la struttura timeout, interfaccia più pulita. Su Linux, epoll è ancora più efficiente per molti fd (O(1) invece di O(n)).
epoll (Linux-specific)
#include <sys/epoll.h>

int epfd = epoll_create1(0);

struct epoll_event ev = { .events = EPOLLIN, .data.fd = fd };
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

struct epoll_event events[10];
int n = epoll_wait(epfd, events, 10, 5000);

for (int i = 0; i < n; i++) {
    if (events[i].events & EPOLLIN)
        handle_read(events[i].data.fd);
}
close(epfd);
PIPE ANONIME
Concetti

Una pipe è un canale unidirezionale (FIFO) tra processi correlati (padre-figlio). Capacità tipica: 64KB (Linux). Lettura da pipe vuota blocca; scrittura su pipe senza lettori genera SIGPIPE.

#include <unistd.h>

int pipe(int pipefd[2]);
// pipefd[0] = fd lettura
// pipefd[1] = fd scrittura
// Ritorna: 0 successo, -1 errore

int pipe2(int pipefd[2], int flags);  // Linux: O_CLOEXEC, O_NONBLOCK
Comunicazione Padre → Figlio
int pfd[2];
pipe(pfd);

pid_t pid = fork();
if (pid == 0) {
    /* FIGLIO: legge dalla pipe */
    close(pfd[1]);  // chiude lato scrittura

    char buf[256];
    ssize_t n = read(pfd[0], buf, sizeof(buf) - 1);
    buf[n] = '\0';
    printf("Figlio riceve: %s\n", buf);

    close(pfd[0]);
    _exit(0);
}

/* PADRE: scrive nella pipe */
close(pfd[0]);  // chiude lato lettura

const char *msg = "Ciao dal padre!";
write(pfd[1], msg, strlen(msg));

close(pfd[1]);
waitpid(pid, NULL, 0);
Chiudere sempre il lato non usato della pipe in ogni processo! Altrimenti read() non riceverà mai EOF e il processo resterà bloccato.
Pipeline: Equivalente di "ls | sort"
int pfd[2];
pipe(pfd);

pid_t pid1 = fork();
if (pid1 == 0) {
    /* Primo comando: ls (scrive su pipe) */
    close(pfd[0]);
    dup2(pfd[1], STDOUT_FILENO);
    close(pfd[1]);
    execlp("ls", "ls", (char*)NULL);
    _exit(127);
}

pid_t pid2 = fork();
if (pid2 == 0) {
    /* Secondo comando: sort (legge da pipe) */
    close(pfd[1]);
    dup2(pfd[0], STDIN_FILENO);
    close(pfd[0]);
    execlp("sort", "sort", (char*)NULL);
    _exit(127);
}

/* Padre: chiude entrambi i lati e attende */
close(pfd[0]);
close(pfd[1]);
waitpid(pid1, NULL, 0);
waitpid(pid2, NULL, 0);
Pipe Bidirezionale (Due Pipe)
/* Per comunicazione bidirezionale servono 2 pipe */
int p2c[2];  // padre → figlio
int c2p[2];  // figlio → padre
pipe(p2c);
pipe(c2p);

pid_t pid = fork();
if (pid == 0) {
    close(p2c[1]);  // figlio non scrive su p2c
    close(c2p[0]);  // figlio non legge da c2p
    // legge da p2c[0], scrive su c2p[1]
    ...
} else {
    close(p2c[0]);  // padre non legge da p2c
    close(c2p[1]);  // padre non scrive su c2p
    // scrive su p2c[1], legge da c2p[0]
    ...
}
FIFO (NAMED PIPE)
Concetti

Una FIFO è una pipe con nome nel filesystem. Permette comunicazione tra processi non correlati. Creata con mkfifo() o mknod(). Si apre con open(). open() blocca finché l'altro lato non apre.

#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
// Crea un file FIFO nel filesystem
// Ritorna: 0 successo, -1 errore (EEXIST se già esiste)
Esempio: Processo Scrittore e Lettore
/* ─── SCRITTORE ─── */
mkfifo("/tmp/myfifo", 0666);

int fd = open("/tmp/myfifo",
              O_WRONLY);
// blocca fino a che un lettore apre

write(fd, "Hello FIFO\n", 11);
close(fd);
/* ─── LETTORE ─── */
int fd = open("/tmp/myfifo",
              O_RDONLY);
// blocca fino a che uno scrittore apre

char buf[128];
ssize_t n = read(fd, buf,
                 sizeof(buf));
close(fd);
unlink("/tmp/myfifo");
Per evitare il blocco su open(), usare O_NONBLOCK. Con O_RDONLY|O_NONBLOCK, open() ritorna subito; con O_WRONLY|O_NONBLOCK, open() fallisce con ENXIO se nessun lettore.
popen() & pclose()
Interfaccia di Alto Livello

popen() combina fork+exec+pipe in una singola chiamata. Esegue un comando shell e ritorna un FILE* collegato alla pipe.

#include <stdio.h>

FILE *popen(const char *command, const char *type);
// type "r" → legge stdout del comando
// type "w" → scrive su stdin del comando

int pclose(FILE *stream);
// Chiude la pipe e attende la terminazione del processo
// Ritorna: exit status del comando (come waitpid)
/* Leggere output di un comando */
FILE *fp = popen("ls -la /tmp", "r");
if (fp == NULL) { perror("popen"); exit(1); }

char line[512];
while (fgets(line, sizeof(line), fp) != NULL) {
    printf(">> %s", line);
}
int status = pclose(fp);
/* Scrivere su stdin di un comando */
FILE *fp = popen("sort > sorted.txt", "w");
fprintf(fp, "banana\napple\ncherry\n");
pclose(fp);
popen() invoca /bin/sh -c command: attenzione all'injection se command contiene input utente non sanitizzato!
SEGNALI POSIX
Tabella Segnali Principali
SegnaleAzione DefaultDescrizione
SIGHUP1TermHangup / terminale chiuso
SIGINT2TermInterrupt (Ctrl+C)
SIGQUIT3CoreQuit (Ctrl+\)
SIGILL4CoreIstruzione illegale
SIGTRAP5CoreTrap (breakpoint)
SIGABRT6CoreAbort (abort())
SIGBUS7CoreBus error (allineamento)
SIGFPE8CoreFloating-point exception
SIGKILL9TermKill (non intercettabile)
SIGUSR110TermUser-defined signal 1
SIGSEGV11CoreSegmentation fault
SIGUSR212TermUser-defined signal 2
SIGPIPE13TermScrittura su pipe senza lettori
SIGALRM14TermTimer (alarm())
SIGTERM15TermTerminazione (educata)
SIGCHLD17IgnFiglio terminato/stoppato
SIGCONT18ContContinua se stoppato
SIGSTOP19StopStop (non intercettabile)
SIGTSTP20StopStop da terminale (Ctrl+Z)
SIGTTIN21StopBackground process legge da tty
SIGTTOU22StopBackground process scrive su tty
SIGURG23IgnDati urgenti su socket
SIGXCPU24CoreLimite CPU superato
SIGXFSZ25CoreLimite dimensione file superato
SIGVTALRM26TermVirtual timer
SIGPROF27TermProfiling timer
SIGWINCH28IgnRidimensionamento finestra
SIGIO29TermI/O asincrono
SIGKILL e SIGSTOP non possono essere intercettati, ignorati o bloccati.
Invio Segnali
#include <signal.h>

int kill(pid_t pid, int sig);
// pid > 0  : segnale al processo pid
// pid == 0 : segnale a tutto il process group
// pid == -1: segnale a tutti i processi (tranne PID 1)
// pid < -1 : segnale al group |pid|

int raise(int sig);           // segnale a se stesso
int killpg(pid_t pgrp, int sig); // segnale al group
unsigned alarm(unsigned sec);   // SIGALRM dopo sec secondi

/* Testare se un processo esiste */
if (kill(pid, 0) == 0)
    printf("Processo %d esiste\n", pid);
signal() — Interfaccia Semplificata (Obsoleta)
#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
// handler: SIG_DFL (default), SIG_IGN (ignora), o funzione

void handler(int sig) {
    /* ATTENZIONE: poche funzioni sono async-signal-safe! */
    write(STDOUT_FILENO, "Segnale!\n", 9);
}
signal(SIGINT, handler);
Usare sigaction() invece di signal(). Il comportamento di signal() varia tra sistemi (reset o non-reset del handler, restart o no delle syscall).
sigaction()
Struttura e Prototipo
#include <signal.h>

struct sigaction {
    void     (*sa_handler)(int);           // handler semplice
    void     (*sa_sigaction)(int, siginfo_t*, void*); // handler esteso
    sigset_t sa_mask;    // segnali bloccati durante handler
    int      sa_flags;   // flag di comportamento
};

int sigaction(int signum, const struct sigaction *act,
              struct sigaction *oldact);
Flag sa_flags
FlagDescrizione
SA_RESTARTRiavvia automaticamente syscall interrotte (read, write, ...)
SA_NOCLDSTOPSIGCHLD solo alla terminazione, non allo stop
SA_NOCLDWAITNon creare zombie per i figli
SA_SIGINFOUsa sa_sigaction invece di sa_handler (info estese)
SA_RESETHANDReset handler a SIG_DFL dopo prima invocazione
SA_NODEFERNon bloccare il segnale durante il proprio handler
Esempio: Handler Semplice
volatile sig_atomic_t got_signal = 0;

void handler(int sig) {
    got_signal = 1;
}

int main(void) {
    struct sigaction sa = {
        .sa_handler = handler,
        .sa_flags   = SA_RESTART  // riavvia syscall interrotte
    };
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);

    while (!got_signal) {
        pause();  // sospende fino a un segnale
    }
    printf("Segnale ricevuto, cleanup...\n");
    return 0;
}
Esempio: Handler con SA_SIGINFO
void handler_info(int sig, siginfo_t *info, void *ctx) {
    /* siginfo_t contiene:
       si_signo  — numero segnale
       si_code   — codice (SI_USER, SI_QUEUE, CLD_EXITED, ...)
       si_pid    — PID del mittente
       si_uid    — UID del mittente
       si_status — exit status (SIGCHLD)
       si_value  — valore allegato (sigqueue)
       si_addr   — indirizzo fault (SIGSEGV, SIGBUS)  */
}

struct sigaction sa = {
    .sa_sigaction = handler_info,
    .sa_flags     = SA_SIGINFO | SA_RESTART
};
sigemptyset(&sa.sa_mask);
sigaction(SIGCHLD, &sa, NULL);
Gestione SIGCHLD (Reap Figli)
void sigchld_handler(int sig) {
    int saved_errno = errno;  // salvare errno!

    /* Raccogliere TUTTI i figli terminati (possono arrivarne più di uno) */
    pid_t pid;
    int status;
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        /* gestisci terminazione di pid */
    }

    errno = saved_errno;  // ripristinare errno!
}
MASCHERE SEGNALI & sigprocmask()
Manipolazione sigset_t
#include <signal.h>

sigset_t set;

sigemptyset(&set);         // set vuoto
sigfillset(&set);          // set con tutti i segnali
sigaddset(&set, SIGINT);   // aggiunge SIGINT
sigdelset(&set, SIGINT);   // rimuove SIGINT
sigismember(&set, SIGINT); // testa appartenenza
sigprocmask()
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
// how:
//   SIG_BLOCK   — aggiunge set alla maschera corrente
//   SIG_UNBLOCK — rimuove set dalla maschera corrente
//   SIG_SETMASK — imposta maschera = set

/* Sezione critica: bloccare SIGINT e SIGTERM */
sigset_t block, old;
sigemptyset(&block);
sigaddset(&block, SIGINT);
sigaddset(&block, SIGTERM);

sigprocmask(SIG_BLOCK, &block, &old);  // blocca

/* ... sezione critica ... */

sigprocmask(SIG_SETMASK, &old, NULL);  // ripristina
sigpending() & sigsuspend()
/* Verificare segnali pendenti (bloccati ma ricevuti) */
sigset_t pending;
sigpending(&pending);
if (sigismember(&pending, SIGINT))
    printf("SIGINT pendente!\n");

/* sigsuspend — pausa atomica con maschera temporanea */
sigset_t empty;
sigemptyset(&empty);
sigsuspend(&empty);  // sospende, tutti i segnali sbloccati
// Atomico: imposta maschera e sospende in un colpo
// Risolve la race condition di: sigprocmask() ... pause()
signalfd() (Linux-specific)
#include <sys/signalfd.h>

sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, NULL);  // blocca prima!

int sfd = signalfd(-1, &mask, SFD_CLOEXEC);

/* Ora leggi i segnali come dati da un fd */
struct signalfd_siginfo info;
read(sfd, &info, sizeof(info));
printf("Ricevuto segnale %d da PID %d\n",
       info.ssi_signo, info.ssi_pid);
signalfd permette di gestire segnali nel loop principale (con poll/epoll) senza handler asincroni — molto più sicuro e prevedibile.
SEGNALI REAL-TIME & sigqueue()
Caratteristiche

I segnali real-time (SIGRTMIN..SIGRTMAX, tipicamente 34-64) rispetto ai segnali standard:

• Sono accodati (non persi se multipli)
• Possono trasportare un dato intero o puntatore (union sigval)
• Sono consegnati in ordine di numero (SIGRTMIN prima)

#include <signal.h>

int sigqueue(pid_t pid, int sig, const union sigval value);

union sigval {
    int   sival_int;   // valore intero
    void *sival_ptr;   // valore puntatore
};
Esempio
/* ─── MITTENTE ─── */
union sigval val = { .sival_int = 42 };
sigqueue(target_pid, SIGRTMIN, val);

/* ─── RICEVENTE (con SA_SIGINFO) ─── */
void rt_handler(int sig, siginfo_t *info, void *ctx) {
    printf("RT signal %d, valore=%d, da PID=%d\n",
           sig, info->si_value.sival_int, info->si_pid);
}

struct sigaction sa = {
    .sa_sigaction = rt_handler,
    .sa_flags     = SA_SIGINFO | SA_RESTART
};
sigemptyset(&sa.sa_mask);
sigaction(SIGRTMIN, &sa, NULL);
PATTERN & IDIOMI SEGNALI
Funzioni Async-Signal-Safe

All'interno di un signal handler si possono chiamare solo funzioni async-signal-safe. Le principali:

_exit, write, read, open, close, fork, execve, waitpid, kill, signal, sigaction, sigprocmask, sigemptyset, sigaddset, alarm, sleep, getpid, getppid

printf, malloc, free, exit NON sono async-signal-safe! Usarle in un handler può causare deadlock o corruzione.
Self-Pipe Trick

Pattern per gestire segnali nel loop principale (con select/poll) senza race condition.

int selfpipe[2];

void sig_handler(int sig) {
    int saved_errno = errno;
    char c = sig;
    write(selfpipe[1], &c, 1);  // write è async-signal-safe
    errno = saved_errno;
}

int main(void) {
    pipe2(selfpipe, O_NONBLOCK | O_CLOEXEC);

    struct sigaction sa = { .sa_handler = sig_handler, .sa_flags = SA_RESTART };
    sigemptyset(&sa.sa_mask);
    sigaction(SIGTERM, &sa, NULL);

    /* Nel main loop, monitora selfpipe[0] con poll/select */
    struct pollfd fds[] = {
        { .fd = selfpipe[0], .events = POLLIN },
        /* ... altri fd ... */
    };
    poll(fds, 1, -1);
    if (fds[0].revents & POLLIN) {
        char c;
        read(selfpipe[0], &c, 1);
        /* gestisci segnale (qui è sicuro usare printf, malloc, etc.) */
    }
}
Timer con setitimer()
#include <sys/time.h>

struct itimerval timer = {
    .it_value    = { .tv_sec = 2, .tv_usec = 0 },  // primo scatto: 2s
    .it_interval = { .tv_sec = 1, .tv_usec = 0 },  // intervallo: 1s
};
setitimer(ITIMER_REAL, &timer, NULL);  // genera SIGALRM

/* ITIMER_REAL    → SIGALRM  (tempo reale)
   ITIMER_VIRTUAL → SIGVTALRM (tempo CPU user)
   ITIMER_PROF    → SIGPROF   (tempo CPU user+sys) */
SYSTEM V IPC — PANORAMICA
Architettura Comune

Tutti gli oggetti System V IPC condividono lo stesso pattern: una chiave (key_t) identifica la risorsa; xxxget() crea/accede; xxxctl() controlla; xxxop() opera.

#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);
// Genera una chiave IPC da un pathname esistente e un intero (0-255)
// Stesso pathname + proj_id → stessa chiave
// Il file deve esistere e avere lo stesso i-node

key_t key = ftok("/tmp/myapp", 'A');
if (key == -1) { perror("ftok"); exit(1); }
Flag Comuni
FlagDescrizione
IPC_CREATCrea l'oggetto se non esiste
IPC_EXCLCon IPC_CREAT: fallisce se già esiste
IPC_RMIDRimuovi l'oggetto (xxxctl)
IPC_STATLeggi struttura di stato (xxxctl)
IPC_SETImposta parametri (xxxctl)
IPC_PRIVATEChiave speciale: crea sempre un nuovo oggetto
Comandi Shell di Ispezione
ipcs           # mostra tutte le risorse IPC
ipcs -m        # solo shared memory
ipcs -s        # solo semafori
ipcs -q        # solo code messaggi
ipcrm -m shmid # rimuove shared memory
ipcrm -s semid # rimuove semaforo
ipcrm -q msqid # rimuove coda messaggi
System V vs POSIX IPC
AspettoSystem VPOSIX
Identificazionekey_t (ftok)Nome stringa ("/nome")
APIxxxget/xxxctl/xxxopxxx_open/xxx_close/xxx_unlink
NamespaceKernel-globalFilesystem-like
PortabilitàPiù vecchia, ubiquaStandard POSIX, API migliore
PuliziaManuale (xxxctl IPC_RMID)unlink + ultima close
NotificaNomq: mq_notify
LinkNessuno-lrt (su alcuni sistemi)
SYSTEM V — SHARED MEMORY
API
#include <sys/shm.h>

int   shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int   shmdt(const void *shmaddr);
int   shmctl(int shmid, int cmd, struct shmid_ds *buf);
Esempio Completo
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

#define SHM_SIZE 4096

key_t key = ftok("/tmp/myapp", 'S');

/* ─── PROCESSO 1: crea e scrive ─── */
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
char *ptr = (char *)shmat(shmid, NULL, 0);  // NULL = kernel sceglie indirizzo

strcpy(ptr, "Hello shared memory!");
shmdt(ptr);  // detach (non distrugge)

/* ─── PROCESSO 2: legge ─── */
int shmid = shmget(key, SHM_SIZE, 0666);  // senza IPC_CREAT
char *ptr = (char *)shmat(shmid, NULL, SHM_RDONLY);
printf("%s\n", ptr);
shmdt(ptr);

/* ─── CLEANUP ─── */
shmctl(shmid, IPC_RMID, NULL);  // distrugge il segmento
La shared memory non ha sincronizzazione integrata. Serve un meccanismo esterno (semaforo, mutex) per evitare race condition.
SYSTEM V — SEMAFORI
API
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, size_t nsops);
int semctl(int semid, int semnum, int cmd, ...);

struct sembuf {
    unsigned short sem_num;  // indice semaforo nel set
    short          sem_op;   // operazione
    short          sem_flg;  // IPC_NOWAIT, SEM_UNDO
};
// sem_op > 0 : incrementa (V / signal / release)
// sem_op < 0 : decrementa se possibile, altrimenti blocca (P / wait / acquire)
// sem_op = 0 : attende che il valore diventi 0
Esempio: Mutex con Semaforo
/* Unione necessaria per semctl su alcuni sistemi */
union semun {
    int              val;
    struct semid_ds *buf;
    unsigned short  *array;
};

key_t key = ftok("/tmp/myapp", 'M');
int semid = semget(key, 1, IPC_CREAT | 0666);

/* Inizializzare a 1 (mutex) */
union semun arg = { .val = 1 };
semctl(semid, 0, SETVAL, arg);

/* P (wait / lock) */
struct sembuf p_op = { .sem_num = 0, .sem_op = -1, .sem_flg = SEM_UNDO };
semop(semid, &p_op, 1);

/* ... sezione critica ... */

/* V (signal / unlock) */
struct sembuf v_op = { .sem_num = 0, .sem_op = 1, .sem_flg = SEM_UNDO };
semop(semid, &v_op, 1);

/* Cleanup */
semctl(semid, 0, IPC_RMID);
SEM_UNDO: il kernel ripristina automaticamente le operazioni se il processo termina senza rilasciare il semaforo. Fondamentale per la robustezza.
Operazioni Multiple (Atomiche)
/* Operare su più semafori atomicamente */
struct sembuf ops[2] = {
    { .sem_num = 0, .sem_op = -1, .sem_flg = SEM_UNDO },  // lock sem 0
    { .sem_num = 1, .sem_op = -1, .sem_flg = SEM_UNDO },  // lock sem 1
};
semop(semid, ops, 2);  // entrambe o nessuna (atomico)
SYSTEM V — CODE MESSAGGI
API
#include <sys/msg.h>

int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

// msgtyp in msgrcv:
//   == 0 : primo messaggio in coda
//   > 0  : primo messaggio con tipo == msgtyp
//   < 0  : primo messaggio con tipo ≤ |msgtyp| (priorità)
Struttura Messaggio
/* Il messaggio DEVE iniziare con un long per il tipo */
struct msgbuf {
    long mtype;       // tipo messaggio (> 0)
    char mtext[256];  // dati (dimensione a piacere)
};
Esempio Completo
key_t key = ftok("/tmp/myapp", 'Q');
int msqid = msgget(key, IPC_CREAT | 0666);

/* ─── MITTENTE ─── */
struct msgbuf msg;
msg.mtype = 1;  // tipo del messaggio
snprintf(msg.mtext, sizeof(msg.mtext), "Ciao coda!");
msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0);

/* ─── RICEVENTE ─── */
struct msgbuf rmsg;
ssize_t n = msgrcv(msqid, &rmsg, sizeof(rmsg.mtext), 1, 0);
//                                  tipo 1 ──┘
printf("Ricevuto (tipo %ld): %s\n", rmsg.mtype, rmsg.mtext);

/* Cleanup */
msgctl(msqid, IPC_RMID, NULL);
Pattern: Multiplexing con Tipi
/* Usare il tipo come ID destinatario (es. PID) */
#define MSG_SERVER  1
#define MSG_CLIENT  2

/* Server riceve solo messaggi tipo 1 */
msgrcv(msqid, &msg, size, MSG_SERVER, 0);

/* Client riceve solo messaggi tipo 2 */
msgrcv(msqid, &msg, size, MSG_CLIENT, 0);

/* Oppure: usare il PID come tipo */
msg.mtype = getpid();  // risposte indirizzate al client
POSIX — SHARED MEMORY
API
#include <sys/mman.h>
#include <fcntl.h>

int  shm_open(const char *name, int oflag, mode_t mode);
int  shm_unlink(const char *name);
// name: deve iniziare con "/" (es. "/myshm")
// Ritorna un fd da usare con ftruncate + mmap
// Compilare con -lrt (su alcuni sistemi)
Esempio Completo
#define SHM_NAME "/my_shared_mem"
#define SHM_SIZE 4096

/* ─── PROCESSO 1: crea e scrive ─── */
int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
ftruncate(fd, SHM_SIZE);  // imposta dimensione

char *ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE,
                 MAP_SHARED, fd, 0);
close(fd);  // fd non più necessario dopo mmap

strcpy(ptr, "Hello POSIX shm!");
munmap(ptr, SHM_SIZE);

/* ─── PROCESSO 2: legge ─── */
int fd = shm_open(SHM_NAME, O_RDONLY, 0);
char *ptr = mmap(NULL, SHM_SIZE, PROT_READ, MAP_SHARED, fd, 0);
close(fd);

printf("%s\n", ptr);
munmap(ptr, SHM_SIZE);

/* ─── CLEANUP ─── */
shm_unlink(SHM_NAME);  // rimuove dal filesystem
POSIX — SEMAFORI
Semafori Named (tra processi)
#include <semaphore.h>

sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned value);
int    sem_close(sem_t *sem);
int    sem_unlink(const char *name);

int    sem_wait(sem_t *sem);       // P (decrementa, blocca se 0)
int    sem_trywait(sem_t *sem);    // P non bloccante (EAGAIN)
int    sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
int    sem_post(sem_t *sem);       // V (incrementa)
int    sem_getvalue(sem_t *sem, int *sval);
Esempio: Mutex POSIX tra Processi
#define SEM_NAME "/my_mutex"

/* Creatore */
sem_t *sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);  // valore iniziale 1

/* In ogni processo: */
sem_t *sem = sem_open(SEM_NAME, 0);  // apre esistente

sem_wait(sem);    // lock
/* ... sezione critica ... */
sem_post(sem);    // unlock

sem_close(sem);
sem_unlink(SEM_NAME);  // rimuove (solo un processo)
Semafori Unnamed (in shared memory)
/* Semaforo unnamed: vive in memoria condivisa */
typedef struct {
    sem_t sem;
    int   data;
} shared_t;

/* In shared memory (mmap o shm_open) */
shared_t *shm = mmap(...);
sem_init(&shm->sem, 1, 1);  // pshared=1 per tra processi

sem_wait(&shm->sem);
shm->data = 42;
sem_post(&shm->sem);

sem_destroy(&shm->sem);  // cleanup
POSIX — CODE MESSAGGI
API
#include <mqueue.h>

mqd_t mq_open(const char *name, int oflag, mode_t mode,
               struct mq_attr *attr);
int   mq_close(mqd_t mqdes);
int   mq_unlink(const char *name);

int     mq_send(mqd_t mqdes, const char *msg, size_t len, unsigned prio);
ssize_t mq_receive(mqd_t mqdes, char *msg, size_t len, unsigned *prio);
int     mq_notify(mqd_t mqdes, const struct sigevent *notification);

struct mq_attr {
    long mq_flags;    // 0 o O_NONBLOCK
    long mq_maxmsg;   // max messaggi in coda
    long mq_msgsize;  // max dimensione messaggio
    long mq_curmsgs;  // messaggi attualmente in coda
};
Esempio
#define MQ_NAME "/my_queue"

struct mq_attr attr = {
    .mq_maxmsg  = 10,
    .mq_msgsize = 256
};

/* ─── MITTENTE ─── */
mqd_t mq = mq_open(MQ_NAME, O_CREAT | O_WRONLY, 0666, &attr);
mq_send(mq, "Hello MQ!", 10, 0);  // priorità 0
mq_close(mq);

/* ─── RICEVENTE ─── */
mqd_t mq = mq_open(MQ_NAME, O_RDONLY);

struct mq_attr a;
mq_getattr(mq, &a);

char *buf = malloc(a.mq_msgsize);
unsigned prio;
ssize_t n = mq_receive(mq, buf, a.mq_msgsize, &prio);
printf("Ricevuto (prio %u): %s\n", prio, buf);

free(buf);
mq_close(mq);
mq_unlink(MQ_NAME);
Compilare con -lrt. Le POSIX mq supportano notifica asincrona tramite mq_notify() (segnale o thread).
mmap() & munmap()
Prototipo
#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);
int   munmap(void *addr, size_t length);
int   msync(void *addr, size_t length, int flags);
Flag
ProtezioneDescrizione
PROT_NONENessun accesso
PROT_READLettura
PROT_WRITEScrittura
PROT_EXECEsecuzione
FlagDescrizione
MAP_SHAREDModifiche visibili ad altri processi (IPC)
MAP_PRIVATECopy-on-write (modifiche private)
MAP_ANONYMOUSNessun file (fd ignorato, -1). Utile per alloc
MAP_FIXEDUsa addr esatto (pericoloso)
Uso Comune
/* 1. Memory-mapped file (lettura rapida) */
int fd = open("data.bin", O_RDONLY);
struct stat sb;
fstat(fd, &sb);
char *data = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
/* accesso diretto: data[0], data[1], ... */
munmap(data, sb.st_size);

/* 2. Memoria anonima condivisa tra fork */
int *shared = mmap(NULL, sizeof(int),
                    PROT_READ | PROT_WRITE,
                    MAP_SHARED | MAP_ANONYMOUS,
                    -1, 0);
*shared = 0;

if (fork() == 0) {
    *shared = 42;  // visibile al padre!
    _exit(0);
}
wait(NULL);
printf("shared = %d\n", *shared);  // 42
munmap(shared, sizeof(int));

/* 3. Allocazione di grandi blocchi (alternativa a malloc) */
void *block = mmap(NULL, 1 << 20,  // 1 MB
                    PROT_READ | PROT_WRITE,
                    MAP_PRIVATE | MAP_ANONYMOUS,
                    -1, 0);
mprotect() — Cambiare Protezione
int mprotect(void *addr, size_t len, int prot);

/* Esempio: rendere una pagina read-only dopo la scrittura */
mprotect(ptr, page_size, PROT_READ);
UNIX DOMAIN SOCKET
Concetti

Unix domain socket: comunicazione locale (stesso host), sia stream (SOCK_STREAM) che datagram (SOCK_DGRAM). Più veloci dei TCP socket (nessun overhead rete). Supportano passaggio fd e credenziali.

#include <sys/socket.h>
#include <sys/un.h>

struct sockaddr_un {
    sa_family_t sun_family;  // AF_UNIX
    char        sun_path[108]; // pathname
};
Server Stream
int sfd = socket(AF_UNIX, SOCK_STREAM, 0);

struct sockaddr_un addr = { .sun_family = AF_UNIX };
strncpy(addr.sun_path, "/tmp/my.sock", sizeof(addr.sun_path) - 1);
unlink("/tmp/my.sock");  // rimuovi vecchio socket se esiste

bind(sfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sfd, 5);

while (1) {
    int cfd = accept(sfd, NULL, NULL);

    char buf[256];
    ssize_t n = read(cfd, buf, sizeof(buf));
    write(cfd, buf, n);  // echo

    close(cfd);
}
close(sfd);
unlink("/tmp/my.sock");
Client Stream
int sfd = socket(AF_UNIX, SOCK_STREAM, 0);

struct sockaddr_un addr = { .sun_family = AF_UNIX };
strncpy(addr.sun_path, "/tmp/my.sock", sizeof(addr.sun_path) - 1);

connect(sfd, (struct sockaddr*)&addr, sizeof(addr));

write(sfd, "Hello!", 6);
char buf[256];
ssize_t n = read(sfd, buf, sizeof(buf));

close(sfd);
Passaggio File Descriptor (SCM_RIGHTS)

Feature unica dei Unix domain socket: inviare file descriptor aperti tra processi non correlati.

/* ─── INVIO fd tramite ancillary data ─── */
void send_fd(int socket, int fd_to_send) {
    struct msghdr msg = {0};
    char buf[1] = {'F'};
    struct iovec io = { .iov_base = buf, .iov_len = 1 };
    msg.msg_iov = &io;
    msg.msg_iovlen = 1;

    char cmsgbuf[CMSG_SPACE(sizeof(int))];
    msg.msg_control = cmsgbuf;
    msg.msg_controllen = sizeof(cmsgbuf);

    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type  = SCM_RIGHTS;
    cmsg->cmsg_len   = CMSG_LEN(sizeof(int));
    memcpy(CMSG_DATA(cmsg), &fd_to_send, sizeof(int));

    sendmsg(socket, &msg, 0);
}

/* ─── RICEZIONE fd ─── */
int recv_fd(int socket) {
    struct msghdr msg = {0};
    char buf[1];
    struct iovec io = { .iov_base = buf, .iov_len = 1 };
    msg.msg_iov = &io;
    msg.msg_iovlen = 1;

    char cmsgbuf[CMSG_SPACE(sizeof(int))];
    msg.msg_control = cmsgbuf;
    msg.msg_controllen = sizeof(cmsgbuf);

    recvmsg(socket, &msg, 0);

    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
    int received_fd;
    memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));
    return received_fd;
}
Abstract Socket (Linux)
/* Su Linux, se sun_path[0] == '\0', il socket è "abstract":
   non crea file nel filesystem, rimosso automaticamente */
struct sockaddr_un addr = { .sun_family = AF_UNIX };
addr.sun_path[0] = '\0';
strcpy(addr.sun_path + 1, "my_abstract_socket");

socklen_t len = offsetof(struct sockaddr_un, sun_path)
              + 1 + strlen("my_abstract_socket");
bind(sfd, (struct sockaddr*)&addr, len);
socketpair()
Pipe Bidirezionale

socketpair() crea una coppia di socket connessi — come una pipe bidirezionale. Utile per comunicazione padre-figlio senza FIFO.

#include <sys/socket.h>

int sv[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
// sv[0] e sv[1] sono bidirezionali e connessi tra loro

pid_t pid = fork();
if (pid == 0) {
    close(sv[0]);
    /* Figlio usa sv[1] */
    write(sv[1], "from child", 10);
    char buf[64];
    read(sv[1], buf, sizeof(buf));
    close(sv[1]);
    _exit(0);
}

close(sv[1]);
/* Padre usa sv[0] */
char buf[64];
read(sv[0], buf, sizeof(buf));
write(sv[0], "from parent", 11);
close(sv[0]);
waitpid(pid, NULL, 0);
FILE LOCKING
flock() — Lock su Intero File
#include <sys/file.h>

int flock(int fd, int operation);
// LOCK_SH — shared lock (lettura, multipli)
// LOCK_EX — exclusive lock (scrittura, singolo)
// LOCK_UN — unlock
// LOCK_NB — non bloccante (OR con SH o EX)

int fd = open("data.db", O_RDWR);
if (flock(fd, LOCK_EX) == 0) {
    /* ... operazioni esclusive ... */
    flock(fd, LOCK_UN);
}
close(fd);  // rilascia automaticamente il lock
fcntl() — Lock su Regione (POSIX)
struct flock {
    short l_type;    // F_RDLCK, F_WRLCK, F_UNLCK
    short l_whence;  // SEEK_SET, SEEK_CUR, SEEK_END
    off_t l_start;   // offset inizio
    off_t l_len;     // lunghezza (0 = fino a EOF)
    pid_t l_pid;     // PID che detiene il lock (F_GETLK)
};

/* Lock esclusivo sui primi 100 byte */
struct flock fl = {
    .l_type   = F_WRLCK,
    .l_whence = SEEK_SET,
    .l_start  = 0,
    .l_len    = 100
};

fcntl(fd, F_SETLKW, &fl);  // bloccante
/* ... modifica ... */
fl.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &fl);   // unlock
flock: advisory lock su intero file, ereditato dai figli via fork, rilasciato alla chiusura dell'ultima copia del fd. fcntl lock: advisory lock su regione, NON ereditato, rilasciato alla chiusura di qualsiasi fd sullo stesso file. Non mischiare i due!
Pattern: PID File (Lock Daemon)
int acquire_pidfile(const char *path) {
    int fd = open(path, O_RDWR | O_CREAT, 0644);
    if (fd < 0) return -1;

    struct flock fl = { .l_type = F_WRLCK, .l_whence = SEEK_SET };
    if (fcntl(fd, F_SETLK, &fl) < 0) {
        close(fd);
        return -1;  // un'altra istanza è già in esecuzione
    }

    ftruncate(fd, 0);
    char buf[32];
    snprintf(buf, sizeof(buf), "%d\n", getpid());
    write(fd, buf, strlen(buf));
    /* Non chiudere fd! Il lock resta finché il processo è vivo */
    return fd;
}
VARIABILI D'AMBIENTE
API
#include <stdlib.h>

char *getenv(const char *name);
int   setenv(const char *name, const char *value, int overwrite);
int   unsetenv(const char *name);
int   putenv(char *string);  // "NAME=value" (deprecated)
int   clearenv(void);        // GNU: svuota ambiente

/* Accesso diretto all'ambiente */
extern char **environ;
for (char **ep = environ; *ep != NULL; ep++)
    printf("%s\n", *ep);

/* Oppure via terzo argomento di main (non standard) */
int main(int argc, char *argv[], char *envp[]);
LIMITI & RISORSE
getrlimit() / setrlimit()
#include <sys/resource.h>

struct rlimit {
    rlim_t rlim_cur;  // soft limit (corrente)
    rlim_t rlim_max;  // hard limit (massimo)
};

int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
RisorsaDescrizione
RLIMIT_NOFILEMax file descriptor aperti
RLIMIT_NPROCMax processi per UID
RLIMIT_FSIZEMax dimensione file (byte)
RLIMIT_DATAMax segmento dati
RLIMIT_STACKMax dimensione stack
RLIMIT_COREMax dimensione core dump
RLIMIT_ASMax spazio di indirizzamento virtuale
RLIMIT_CPUMax tempo CPU (secondi)
/* Esempio: limitare la dimensione del core dump */
struct rlimit rl = { .rlim_cur = 0, .rlim_max = 0 };
setrlimit(RLIMIT_CORE, &rl);  // disabilita core dump

/* Aumentare max fd aperti */
struct rlimit rl;
getrlimit(RLIMIT_NOFILE, &rl);
rl.rlim_cur = rl.rlim_max;  // soft = hard
setrlimit(RLIMIT_NOFILE, &rl);
sysconf() e pathconf()
#include <unistd.h>

long nproc = sysconf(_SC_NPROCESSORS_ONLN);
long page  = sysconf(_SC_PAGESIZE);
long maxfd = sysconf(_SC_OPEN_MAX);
long clktk = sysconf(_SC_CLK_TCK);

long maxname = pathconf("/", _PC_NAME_MAX);
long maxpath = pathconf("/", _PC_PATH_MAX);
getrusage()
#include <sys/resource.h>

struct rusage usage;
getrusage(RUSAGE_SELF, &usage);      // processo corrente
getrusage(RUSAGE_CHILDREN, &usage);  // figli terminati

printf("User CPU: %ld.%06ld s\n",
       usage.ru_utime.tv_sec, usage.ru_utime.tv_usec);
printf("Sys CPU:  %ld.%06ld s\n",
       usage.ru_stime.tv_sec, usage.ru_stime.tv_usec);
printf("Max RSS:  %ld KB\n", usage.ru_maxrss);
ptrace()
Panoramica

ptrace() è la syscall alla base di debugger (GDB), strace e strumenti di analisi. Permette a un processo di controllare l'esecuzione di un altro.

#include <sys/ptrace.h>

long ptrace(enum __ptrace_request request, pid_t pid,
            void *addr, void *data);
RequestDescrizione
PTRACE_TRACEMEIl processo corrente viene tracciato dal padre
PTRACE_ATTACHAttacca a un processo esistente
PTRACE_DETACHStacca dal processo tracciato
PTRACE_PEEKTEXTLegge una word dallo spazio testo
PTRACE_PEEKDATALegge una word dallo spazio dati
PTRACE_POKETEXTScrive una word nello spazio testo
PTRACE_GETREGSLegge i registri del processo
PTRACE_SETREGSScrive i registri del processo
PTRACE_SINGLESTEPEsegue una singola istruzione
PTRACE_CONTContinua l'esecuzione
PTRACE_SYSCALLContinua, stop alla prossima syscall
Esempio: Tracer Minimo
pid_t pid = fork();
if (pid == 0) {
    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
    execl("/bin/ls", "ls", (char*)NULL);
    _exit(127);
}

int status;
waitpid(pid, &status, 0);  // stop dopo exec

while (WIFSTOPPED(status)) {
    /* Leggi registri, analizza, ... */
    ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
    waitpid(pid, &status, 0);
}
PATTERN ARCHITETTURALI
Pre-fork Server

Pattern classico per server concorrenti: il padre crea N worker prima di accettare connessioni. Ogni worker chiama accept() in loop.

int listenfd = socket_bind_listen(...);

for (int i = 0; i < N_WORKERS; i++) {
    if (fork() == 0) {
        /* Worker: accetta e gestisce connessioni */
        while (1) {
            int connfd = accept(listenfd, NULL, NULL);
            handle_connection(connfd);
            close(connfd);
        }
    }
}
/* Padre: monitora worker, riavvia se necessario */
while (1) {
    pid_t w = wait(NULL);
    /* Worker crashato: forkare un sostituto */
    if (fork() == 0) { /* nuovo worker... */ }
}
Pipeline di Processi (N stadi)
/* Pipeline generica: cmd[0] | cmd[1] | ... | cmd[n-1] */
void run_pipeline(char **cmds[], int n) {
    int prev_fd = -1;

    for (int i = 0; i < n; i++) {
        int pfd[2];
        if (i < n - 1) pipe(pfd);  // non serve per l'ultimo

        pid_t pid = fork();
        if (pid == 0) {
            if (prev_fd != -1) {
                dup2(prev_fd, STDIN_FILENO);
                close(prev_fd);
            }
            if (i < n - 1) {
                close(pfd[0]);
                dup2(pfd[1], STDOUT_FILENO);
                close(pfd[1]);
            }
            execvp(cmds[i][0], cmds[i]);
            _exit(127);
        }

        if (prev_fd != -1) close(prev_fd);
        if (i < n - 1) {
            close(pfd[1]);
            prev_fd = pfd[0];
        }
    }

    for (int i = 0; i < n; i++) wait(NULL);
}
Producer-Consumer con Shared Memory
typedef struct {
    sem_t mutex;        // accesso esclusivo
    sem_t empty;        // slot liberi
    sem_t full;         // slot pieni
    int   buf[16];
    int   in, out;
} ring_buf_t;

/* Inizializzazione (in shared memory) */
ring_buf_t *rb = mmap(..., MAP_SHARED | MAP_ANONYMOUS, ...);
sem_init(&rb->mutex, 1, 1);
sem_init(&rb->empty, 1, 16);
sem_init(&rb->full,  1, 0);
rb->in = rb->out = 0;

/* PRODUCER */
sem_wait(&rb->empty);
sem_wait(&rb->mutex);
rb->buf[rb->in] = item;
rb->in = (rb->in + 1) % 16;
sem_post(&rb->mutex);
sem_post(&rb->full);

/* CONSUMER */
sem_wait(&rb->full);
sem_wait(&rb->mutex);
int item = rb->buf[rb->out];
rb->out = (rb->out + 1) % 16;
sem_post(&rb->mutex);
sem_post(&rb->empty);
Graceful Shutdown
volatile sig_atomic_t running = 1;

void shutdown_handler(int sig) { running = 0; }

int main(void) {
    struct sigaction sa = { .sa_handler = shutdown_handler };
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);

    /* Blocca SIGINT/SIGTERM tranne in pselect */
    sigset_t block, empty;
    sigemptyset(&empty);
    sigemptyset(&block);
    sigaddset(&block, SIGINT);
    sigaddset(&block, SIGTERM);
    sigprocmask(SIG_BLOCK, &block, NULL);

    while (running) {
        /* pselect sblocca atomicamente i segnali durante l'attesa */
        int ret = pselect(maxfd + 1, &readfds, NULL, NULL, NULL, &empty);
        if (ret > 0) {
            /* gestisci I/O */
        }
    }

    /* Cleanup: chiudi fd, rimuovi shm, termina figli */
    printf("Shutdown completo.\n");
    return 0;
}
GESTIONE ERRORI
errno e Funzioni Correlate
#include <errno.h>
#include <string.h>
#include <stdio.h>

/* errno è thread-local: ogni thread ha il suo */
extern int errno;

perror("open");                  // "open: No such file or directory"
fprintf(stderr, "%s\n",
        strerror(errno));         // "No such file or directory"
Codici errno Comuni per IPC
errnoContesto
EACCESPermessi insufficienti
EEXISTRisorsa già esistente (IPC_EXCL, O_EXCL)
ENOENTRisorsa non trovata
EINTRSyscall interrotta da segnale
EAGAINOperazione non bloccante non completabile
ENOMEMMemoria insufficiente
EMFILETroppi file aperti (per processo)
ENFILETroppi file aperti (sistema)
EPIPEScrittura su pipe/socket chiuso
ECHILDNessun figlio da attendere
ESRCHProcesso non trovato (kill)
ENOSPCSpazio insufficiente (disco, IPC)
ERANGERisultato fuori range
EDEADLKDeadlock evitato (file lock)
Macro di Utility
#define CHECK(call, msg) do { \
    if ((call) == -1) {       \
        perror(msg);            \
        exit(EXIT_FAILURE);     \
    }                           \
} while(0)

/* Uso */
CHECK(pipe(pfd), "pipe");
CHECK(pid = fork(), "fork");

/* Retry su EINTR */
#define RETRY_EINTR(expr) ({ \
    typeof(expr) _r;          \
    do { _r = (expr); }        \
    while (_r == -1 && errno == EINTR); \
    _r; })

ssize_t n = RETRY_EINTR(read(fd, buf, size));