Ogni processo UNIX ha: PID, PPID, UID/GID, tabella file descriptor, spazio di indirizzamento virtuale, segnali pendenti, priority/nice, environment.
| Header | Funzionalità |
|---|---|
<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) |
#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() 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)
#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; }
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)
/* 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); }
exit() nel figlio, ogni figlio continuerebbe il loop e creerebbe a sua volta altri figli (fork bomb).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 }
Le funzioni exec sostituiscono l'immagine del processo corrente con un nuovo programma. Non ritornano in caso di successo.
| Funzione | Path/Name | Argomenti | Ambiente |
|---|---|---|---|
execl() | Pathname | Lista (variadic) | Ereditato |
execlp() | Cerca in PATH | Lista (variadic) | Ereditato |
execle() | Pathname | Lista (variadic) | Esplicito (envp) |
execv() | Pathname | Array argv[] | Ereditato |
execvp() | Cerca in PATH | Array argv[] | Ereditato |
execvpe() | Cerca in PATH | Array argv[] | Esplicito (envp) |
execve() | Pathname | Array argv[] | Esplicito (envp) |
l=list, v=vector, p=PATH, e=environment. execve() è la syscall reale, le altre sono wrapper libc./* 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);
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);
fork(), nel figlio usare _exit() (non exit()) per evitare il flush dei buffer stdio ereditati dal padre.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.
#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|
| Flag | Descrizione |
|---|---|
0 | Bloccante (attende terminazione) |
WNOHANG | Non bloccante: ritorna 0 se nessun figlio terminato |
WUNTRACED | Ritorna anche per figli stoppati (non solo terminati) |
WCONTINUED | Ritorna anche per figli riavviati con SIGCONT |
| Macro | Descrizione |
|---|---|
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 |
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)); }
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
| Funzione | Header | Flush stdio | atexit() | Uso |
|---|---|---|---|---|
exit(status) | <stdlib.h> | Sì | Esegue | Terminazione normale |
_exit(status) | <unistd.h> | No | No | Dopo fork(), in caso di errore exec |
_Exit(status) | <stdlib.h> | No | No | Equivalente C99 di _exit |
#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" }
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)
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
#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) }
daemon(0, 0) (non-standard) esegue gli step 1-5 automaticamente.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:
| fd | Costante | Stream C | Descrizione |
|---|---|---|---|
0 | STDIN_FILENO | stdin | Standard input |
1 | STDOUT_FILENO | stdout | Standard output |
2 | STDERR_FILENO | stderr | Standard 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 │ └──────────────────┘
#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 | Descrizione |
|---|---|
O_RDONLY | Sola lettura |
O_WRONLY | Sola scrittura |
O_RDWR | Lettura e scrittura |
O_CREAT | Crea il file se non esiste (richiede mode) |
O_EXCL | Con O_CREAT: fallisce se il file esiste (atomico) |
O_TRUNC | Tronca a 0 byte se esiste |
O_APPEND | Scrittura atomica in append |
O_NONBLOCK | I/O non bloccante |
O_CLOEXEC | Chiudi automaticamente su exec (atomico) |
O_SYNC | Scrittura sincrona (flush su disco) |
O_DIRECTORY | Fallisce se non è una directory |
O_NOFOLLOW | Non seguire symlink |
/* 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 */
#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
#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)
/* 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; }
#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);
#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
/* 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");
/* 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);
#include <fcntl.h> int fcntl(int fd, int cmd, ... );
| Comando | Argomento | Descrizione |
|---|---|---|
F_DUPFD | int minfd | Duplica fd (≥ minfd) |
F_DUPFD_CLOEXEC | int minfd | Duplica con O_CLOEXEC |
F_GETFD | — | Ottiene fd flags (FD_CLOEXEC) |
F_SETFD | int flags | Imposta fd flags |
F_GETFL | — | Ottiene file status flags |
F_SETFL | int flags | Imposta file status flags (O_NONBLOCK, O_APPEND) |
F_SETLK | struct flock* | Lock non bloccante (file locking) |
F_SETLKW | struct flock* | Lock bloccante (attende) |
F_GETLK | struct flock* | Testa se un lock è possibile |
/* 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);
#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"); }
#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)).#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);
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
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);
read() non riceverà mai EOF e il processo resterà bloccato.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);
/* 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] ... }
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)
/* ─── 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");
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() 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!| Segnale | N° | Azione Default | Descrizione |
|---|---|---|---|
SIGHUP | 1 | Term | Hangup / terminale chiuso |
SIGINT | 2 | Term | Interrupt (Ctrl+C) |
SIGQUIT | 3 | Core | Quit (Ctrl+\) |
SIGILL | 4 | Core | Istruzione illegale |
SIGTRAP | 5 | Core | Trap (breakpoint) |
SIGABRT | 6 | Core | Abort (abort()) |
SIGBUS | 7 | Core | Bus error (allineamento) |
SIGFPE | 8 | Core | Floating-point exception |
SIGKILL | 9 | Term | Kill (non intercettabile) |
SIGUSR1 | 10 | Term | User-defined signal 1 |
SIGSEGV | 11 | Core | Segmentation fault |
SIGUSR2 | 12 | Term | User-defined signal 2 |
SIGPIPE | 13 | Term | Scrittura su pipe senza lettori |
SIGALRM | 14 | Term | Timer (alarm()) |
SIGTERM | 15 | Term | Terminazione (educata) |
SIGCHLD | 17 | Ign | Figlio terminato/stoppato |
SIGCONT | 18 | Cont | Continua se stoppato |
SIGSTOP | 19 | Stop | Stop (non intercettabile) |
SIGTSTP | 20 | Stop | Stop da terminale (Ctrl+Z) |
SIGTTIN | 21 | Stop | Background process legge da tty |
SIGTTOU | 22 | Stop | Background process scrive su tty |
SIGURG | 23 | Ign | Dati urgenti su socket |
SIGXCPU | 24 | Core | Limite CPU superato |
SIGXFSZ | 25 | Core | Limite dimensione file superato |
SIGVTALRM | 26 | Term | Virtual timer |
SIGPROF | 27 | Term | Profiling timer |
SIGWINCH | 28 | Ign | Ridimensionamento finestra |
SIGIO | 29 | Term | I/O asincrono |
SIGKILL e SIGSTOP non possono essere intercettati, ignorati o bloccati.#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);
#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);
sigaction() invece di signal(). Il comportamento di signal() varia tra sistemi (reset o non-reset del handler, restart o no delle syscall).#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 | Descrizione |
|---|---|
SA_RESTART | Riavvia automaticamente syscall interrotte (read, write, ...) |
SA_NOCLDSTOP | SIGCHLD solo alla terminazione, non allo stop |
SA_NOCLDWAIT | Non creare zombie per i figli |
SA_SIGINFO | Usa sa_sigaction invece di sa_handler (info estese) |
SA_RESETHAND | Reset handler a SIG_DFL dopo prima invocazione |
SA_NODEFER | Non bloccare il segnale durante il proprio handler |
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; }
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);
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! }
#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
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
/* 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()
#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.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 };
/* ─── 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);
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.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.) */ } }
#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) */
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 | Descrizione |
|---|---|
IPC_CREAT | Crea l'oggetto se non esiste |
IPC_EXCL | Con IPC_CREAT: fallisce se già esiste |
IPC_RMID | Rimuovi l'oggetto (xxxctl) |
IPC_STAT | Leggi struttura di stato (xxxctl) |
IPC_SET | Imposta parametri (xxxctl) |
IPC_PRIVATE | Chiave speciale: crea sempre un nuovo oggetto |
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
| Aspetto | System V | POSIX |
|---|---|---|
| Identificazione | key_t (ftok) | Nome stringa ("/nome") |
| API | xxxget/xxxctl/xxxop | xxx_open/xxx_close/xxx_unlink |
| Namespace | Kernel-global | Filesystem-like |
| Portabilità | Più vecchia, ubiqua | Standard POSIX, API migliore |
| Pulizia | Manuale (xxxctl IPC_RMID) | unlink + ultima close |
| Notifica | No | mq: mq_notify |
| Link | Nessuno | -lrt (su alcuni sistemi) |
#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);
#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
#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
/* 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./* 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)
#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à)
/* Il messaggio DEVE iniziare con un long per il tipo */ struct msgbuf { long mtype; // tipo messaggio (> 0) char mtext[256]; // dati (dimensione a piacere) };
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);
/* 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
#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)
#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
#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);
#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)
/* 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
#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 };
#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);
-lrt. Le POSIX mq supportano notifica asincrona tramite mq_notify() (segnale o thread).#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);
| Protezione | Descrizione |
|---|---|
PROT_NONE | Nessun accesso |
PROT_READ | Lettura |
PROT_WRITE | Scrittura |
PROT_EXEC | Esecuzione |
| Flag | Descrizione |
|---|---|
MAP_SHARED | Modifiche visibili ad altri processi (IPC) |
MAP_PRIVATE | Copy-on-write (modifiche private) |
MAP_ANONYMOUS | Nessun file (fd ignorato, -1). Utile per alloc |
MAP_FIXED | Usa addr esatto (pericoloso) |
/* 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);
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: 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 };
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");
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);
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; }
/* 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() 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);
#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
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
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; }
#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[]);
#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);
| Risorsa | Descrizione |
|---|---|
RLIMIT_NOFILE | Max file descriptor aperti |
RLIMIT_NPROC | Max processi per UID |
RLIMIT_FSIZE | Max dimensione file (byte) |
RLIMIT_DATA | Max segmento dati |
RLIMIT_STACK | Max dimensione stack |
RLIMIT_CORE | Max dimensione core dump |
RLIMIT_AS | Max spazio di indirizzamento virtuale |
RLIMIT_CPU | Max 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);
#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);
#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() è 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);
| Request | Descrizione |
|---|---|
PTRACE_TRACEME | Il processo corrente viene tracciato dal padre |
PTRACE_ATTACH | Attacca a un processo esistente |
PTRACE_DETACH | Stacca dal processo tracciato |
PTRACE_PEEKTEXT | Legge una word dallo spazio testo |
PTRACE_PEEKDATA | Legge una word dallo spazio dati |
PTRACE_POKETEXT | Scrive una word nello spazio testo |
PTRACE_GETREGS | Legge i registri del processo |
PTRACE_SETREGS | Scrive i registri del processo |
PTRACE_SINGLESTEP | Esegue una singola istruzione |
PTRACE_CONT | Continua l'esecuzione |
PTRACE_SYSCALL | Continua, stop alla prossima syscall |
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 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 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); }
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);
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; }
#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"
| errno | Contesto |
|---|---|
EACCES | Permessi insufficienti |
EEXIST | Risorsa già esistente (IPC_EXCL, O_EXCL) |
ENOENT | Risorsa non trovata |
EINTR | Syscall interrotta da segnale |
EAGAIN | Operazione non bloccante non completabile |
ENOMEM | Memoria insufficiente |
EMFILE | Troppi file aperti (per processo) |
ENFILE | Troppi file aperti (sistema) |
EPIPE | Scrittura su pipe/socket chiuso |
ECHILD | Nessun figlio da attendere |
ESRCH | Processo non trovato (kill) |
ENOSPC | Spazio insufficiente (disco, IPC) |
ERANGE | Risultato fuori range |
EDEADLK | Deadlock evitato (file lock) |
#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));