Un servizio sotto carico smette di accettare connessioni e nei registri compare Too many open files. Un programma impazzito riempie la memoria. Un file di prova cresce senza controllo. Linux ha una difesa per tutti questi casi: ogni processo ha dei limiti sulle risorse che può usare. ulimit permette di vederli e di cambiarli.
Cos'è
ulimit ("limite d'uso") non è un programma a sé, ma un comando interno della shell (bash): non si trova in /usr/bin, e type ulimit risponde ulimit is a shell builtin. Il motivo è tecnico e importante: i limiti sono una proprietà del processo, e un processo figlio li eredita dal padre. Per cambiare i limiti di una sessione bisogna quindi cambiarli nella shell che la gestisce, e valgono poi per tutto ciò che si avvia da lì.
Cosa fa
Mostra i limiti attuali (quanti file si possono aprire, quanti processi, quanta memoria, quanto tempo di processore, quanto può essere grande un file) e permette di abbassarli, o di alzarli entro un massimo. Ogni limite ha due valori: un limite morbido (soft, quello in vigore) e un limite rigido (hard, il tetto fino al quale il morbido può essere alzato).
Come si usa
$ ulimit [opzioni] [valore]Gli esempi sono stati provati in un sandbox; i valori mostrati negli elenchi sono quelli tipici di un server Debian, e sul tuo possono essere diversi.
Esempio 1: vedere tutti i limiti, con -a
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
file size (blocks, -f) unlimited
pending signals (-i) 31523
max locked memory (kbytes, -l) 8192
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 31523
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimitedL'elenco mostra, per ogni risorsa, l'unità (blocchi, kibibyte, secondi) e la lettera dell'opzione da usare per leggerlo o cambiarlo. Le voci che interessano più spesso: open files (-n), i file che un processo può avere aperti insieme (compresi i collegamenti di rete: vedi l'articolo su lsof); max user processes (-u), quanti processi può avere un utente; core file size (-c), la dimensione degli "scarichi di memoria" lasciati da un programma che si blocca; file size (-f), la dimensione massima di un file; cpu time (-t), i secondi di processore; virtual memory (-v), la memoria. unlimited significa "nessun limite". Attenzione: un limite non è una garanzia, ma solo un tetto (vedi l'articolo su free per la memoria realmente disponibile).
Esempio 2: limite morbido e rigido
$ ulimit -n
1024
$ ulimit -Sn
1024
$ ulimit -Hn
1048576
$ ulimit -Sa | head -n 3
$ ulimit -Ha | head -n 3Con -S (soft) si legge il limite morbido, con -H (hard) quello rigido; senza nessuna delle due, in lettura, ulimit mostra il morbido. Il primo è quello che i programmi trovano quando partono, il secondo è il tetto: un utente normale può portare il morbido fino al rigido, ma non oltre. Qui il morbido è 1024 e il rigido più di un milione: significa che puoi alzare il limite dei file aperti molto in alto senza essere amministratore. Le opzioni -Sa e -Ha mostrano l'elenco completo dei due tipi.
Esempio 3: cambiare un limite
$ ulimit -Sn 4096
$ ulimit -n
4096
$ ulimit -Sn 2000000
bash: ulimit: open files: cannot modify limit: Invalid argument
$ ( ulimit -Sn 8192; ./mio-servizio )
$ ulimit -n
4096Con un valore, ulimit imposta il limite. Il cambiamento vale per la shell attuale e per tutto ciò che si avvia da qui in poi; non per le altre finestre, non per i programmi già in corso. Un valore morbido superiore al rigido è rifiutato (Invalid argument). Racchiudendo il comando tra parentesi tonde si crea una sottoshell temporanea: il limite cambia solo per ./mio-servizio e la shell principale resta com'era (l'ultimo ulimit -n mostra ancora 4096). È il modo più pulito di dare un limite diverso a un singolo comando.
Esempio 4: l'errore "Too many open files"
$ ( ulimit -n 20; for i in $(seq 1 30); do exec {fd}<>/dev/null || break; done )
bash: /dev/null: Too many open files
$ python3 -c "import resource; print(resource.getrlimit(resource.RLIMIT_NOFILE))"
(1024, 1048576)È l'errore più frequente legato ai limiti: un programma ha raggiunto il numero massimo di file aperti, e ogni nuova apertura (di un file, di una connessione di rete, di un canale) fallisce con Too many open files ([Errno 24] in Python). Il primo esempio lo provoca di proposito con un limite di 20: exec {fd}<>/dev/null apre ripetutamente un file finché il limite non è raggiunto. Succede ai server web e ai database con molte connessioni, e ai programmi che aprono file senza richiuderli. Il secondo comando mostra come un programma Python vede i suoi limiti (morbido e rigido).
Esempio 5: i limiti di un processo già in corso
$ cat /proc/1034/limits
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max core file size 0 unlimited bytes
Max processes 31523 31523 processes
Max open files 1024 524288 files
...
$ ls /proc/1034/fd | wc -l
312
$ sudo prlimit --pid 1034 --nofile
RESOURCE DESCRIPTION SOFT HARD UNITS
NOFILE max number of open files 1024 524288 files
$ sudo prlimit --pid 1034 --nofile=65535:65535ulimit parla solo della tua shell. Per un programma già avviato (come il database con il PID 1034, o nginx) i limiti si leggono in /proc/PID/limits, e il numero di file che ha aperti in questo momento si conta con ls /proc/PID/fd | wc -l (vedi l'articolo su wc): qui 312 su 1024, quindi il programma sta usando un terzo del suo limite. Il comando prlimit (del pacchetto util-linux, come lsblk e mount) legge e cambia i limiti di un processo in corso, senza riavviarlo; per i processi di altri utenti serve sudo. La forma --nofile=morbido:rigido imposta i due valori. Un rimedio d'emergenza: vale finché il processo vive, poi si deve sistemare la configurazione (esempio 8).
Esempio 6: limiti per i programmi che si comportano male
$ ( ulimit -f 100; dd if=/dev/zero of=grande bs=1k count=500 )
File size limit exceeded
$ ls -l grande | awk '{print $5}'
102400
$ ( ulimit -v 100000; python3 -c "x = bytearray(300*1024*1024)" )
MemoryError
$ ( ulimit -t 1; while :; do :; done )
KilledI limiti sono utili anche per proteggersi da errori. Con -f (qui in blocchi da 1024 byte) un comando non può creare file più grandi del limite: dd si ferma a 102400 byte (100 blocchi) invece di scrivere i 500 richiesti, ricevendo il segnale File size limit exceeded. Con -v (kibibyte) un programma non può usare più memoria virtuale: il programma Python di prova, che ne chiede 300 MiB con un tetto di circa 98 MiB, riceve un errore MemoryError. Con -t (secondi di processore) un ciclo infinito viene ucciso dopo un secondo di lavoro. Sono esempi pensati per essere sicuri: puoi provarli senza rischi, perché ogni limite vale solo dentro le parentesi.
Esempio 7: i file di scarico (core dump)
$ ulimit -c
0
$ ulimit -c unlimited
$ ./programma-che-si-blocca
Segmentation fault (core dumped)
$ coredumpctl list | tail -n 2
$ ulimit -c 0Quando un programma si blocca per un errore grave, il sistema può salvare un "core dump", una copia della memoria, utile agli sviluppatori per capire il guasto. Normalmente ulimit -c vale 0 (niente scarichi), perché i file possono essere molto grandi e contenere dati riservati, come password. Per diagnosticare un problema si alza a unlimited solo per la sessione di prova, e poi si rimette a 0. Dove vengono scritti dipende dal sistema: sulle installazioni con systemd-coredump si consultano con coredumpctl.
Esempio 8: rendere i limiti permanenti
$ sudo nano /etc/security/limits.d/90-enzo.conf
enzo soft nofile 8192
enzo hard nofile 65536
* soft core 0
$ sudo systemctl edit nginx
[Service]
LimitNOFILE=65535
$ sudo systemctl restart nginx
$ systemctl show nginx -p LimitNOFILE
LimitNOFILE=65535
$ grep 'open files' /proc/$(systemctl show -p MainPID --value nginx)/limitsI limiti cambiati con ulimit spariscono alla chiusura della sessione. Per renderli permanenti ci sono due strade diverse, a seconda di chi deve rispettarli. Per le sessioni degli utenti (l'accesso con ssh, il terminale) si scrive in /etc/security/limits.conf o in un file sotto /etc/security/limits.d/: le colonne sono utente (o * per tutti), tipo (soft o hard), risorsa (nofile per i file aperti, nproc, core…) e valore; valgono dal prossimo accesso, tramite il modulo pam_limits. Per i servizi avviati da systemd (come nginx, mariadb), invece, limits.conf non conta: si usa la direttiva LimitNOFILE= (e simili: LimitNPROC=, LimitCORE=) in un file di personalizzazione, creato con systemctl edit (vedi l'articolo su systemctl) e seguito da un riavvio. Con systemctl show si verifica il valore, e con /proc/PID/limits si controlla che il processo l'abbia davvero ricevuto. I nomi dei file e i valori sono esempi.
Esempio 9: uso negli script
#!/bin/bash
# Alza il limite morbido dei file aperti al massimo consentito
ulimit -Sn "$(ulimit -Hn)"
echo "Limite file aperti: $(ulimit -n)"
exec /usr/local/bin/mio-servizio
# Controllo: quanti file ha aperti un processo rispetto al suo limite?
PID=$1
LIMITE=$(awk '/Max open files/ {print $4}' /proc/"$PID"/limits)
USATI=$(ls /proc/"$PID"/fd | wc -l)
echo "File aperti: $USATI su $LIMITE"Il primo blocco è uno "script di avvio": porta il limite morbido al valore del rigido (ulimit -Sn "$(ulimit -Hn)") e poi lancia il servizio con exec, che lo sostituisce allo script, ereditando il limite. È un modo semplice di risolvere "Too many open files" per un programma lanciato a mano. Il secondo blocco è uno strumento di controllo: legge il limite dal file in /proc (con awk: vedi l'articolo relativo, $4 è il valore morbido) e lo confronta con i file aperti. L'ho provato sulla shell stessa (5 file aperti su 20000). Programmato con crontab, e con un avviso quando il rapporto supera l'80%, è un buon allarme.
Opzioni utili
-a: tutti i limiti;-S/-H: limite morbido / rigido (con-aper l'elenco).-n: file aperti;-u: processi dell'utente;-c: dimensione dei core dump;-f: dimensione massima dei file.-t: secondi di processore;-v: memoria virtuale;-s: dimensione dello stack;-l: memoria bloccata.unlimited: nessun limite;( ulimit ...; comando ): limite solo per un comando.cat /proc/PID/limits,prlimit --pid PID: limiti di un processo in corso (prlimitli può anche cambiare)./etc/security/limits.conf,/etc/security/limits.d/: limiti permanenti per le sessioni;LimitNOFILE=e simili: per i servizisystemd.sysctl fs.file-max,/proc/sys/fs/file-nr: limiti e uso dei file aperti di tutto il sistema.
Attenzione a
- Senza
-So-H, scrivendo un valore si cambiano entrambi i limiti:ulimit -n 50imposta sia il morbido che il rigido a 50, e poi non puoi più rialzarlo (si ottieneOperation not permitted), se non chiudendo la shell. Per abbassare o alzare solo il morbido usa sempre-S:ulimit -Sn 4096. - Il rigido non si alza: un utente normale può solo abbassarlo. Per alzarlo serve un accesso privilegiato o la configurazione (esempio 8). Se il morbido è più alto del nuovo rigido,
ulimitrispondeInvalid argument. - Vale per la sessione e i suoi figli, non per il sistema: gli altri accessi, i programmi già avviati e i servizi di
systemdnon cambiano. Per un servizio in funzione, usaprlimit(temporaneo) oLimitNOFILE=(permanente). limits.confnon vale per i servizi: è la confusione più comune. Modifichi il file, riavvii il servizio, e il limite non cambia: quel file lo leggono solo le sessioni che passano dapam_limits(accesso,su,sudo), non i processi avviati direttamente dasystemd.- Le unità non sono uniformi:
-fè in blocchi (da 1024 byte inbash),-ve-din kibibyte,-tin secondi,-ne-usono semplici numeri. Controlla l'unità nell'elenco diulimit -aprima di scrivere un valore. - Un limite troppo basso rompe i programmi in modo oscuro: un tetto di memoria o di processi troppo stretto produce errori strani (
Cannot allocate memory,Resource temporarily unavailable) e crash. Prima di applicarlo a un servizio, provalo in una sottoshell. - Alzare "a
unlimited" non è una buona cura: nasconde una perdita di file o di memoria del programma, che prima o poi riempirà comunque il sistema. Se i file aperti crescono sempre, il difetto è nel programma: controlla conlsofe/proc/PID/fd. - Il limite dei processi è per utente:
-uconta tutti i processi dell'utente, non quelli della sessione; è una difesa contro i programmi che si moltiplicano senza controllo, e può bloccare i nuovi comandi dello stesso utente (fork: Resource temporarily unavailable). L'amministratore è di solito esentato. - Esistono anche limiti di tutto il sistema: il numero massimo di file aperti dal sistema intero (
fs.file-max) è un'altra cosa, e si vede concat /proc/sys/fs/file-nr. I limiti diulimitsono per processo e restano sotto quello. - Nei container i limiti si impostano in altro modo (con Docker,
--ulimit nofile=65535:65535) e spesso non sono quelli della macchina ospite. - Non sostituisce un controllo vero delle risorse: per limitare con precisione memoria e processore di un servizio,
systemdoffreMemoryMax=eCPUQuota=, che sono più adatti diulimit.
Riepilogo
| Comando | Cosa fa |
|---|---|
ulimit -a | Mostra tutti i limiti della sessione |
ulimit -Sn / ulimit -Hn | Limite morbido / rigido dei file aperti |
ulimit -Sn 4096 | Alza (o abbassa) il limite morbido, solo per questa sessione |
( ulimit -Sn 8192; comando ) | Limite diverso per un solo comando |
ulimit -c unlimited | Abilita i core dump (per la diagnosi) |
ulimit -f 100 | File non più grandi di 100 blocchi da 1 KiB |
ulimit -t 60 | Massimo 60 secondi di processore |
cat /proc/PID/limits | Limiti di un processo in corso |
ls /proc/PID/fd | wc -l | File che il processo ha aperti ora |
sudo prlimit --pid PID --nofile=N:N | Cambia i limiti di un processo in corso |
/etc/security/limits.d/*.conf | Limiti permanenti per le sessioni degli utenti |
LimitNOFILE= (systemctl edit) | Limite permanente per un servizio systemd |