Introduction
Sous Linux et les systèmes UNIX, chaque processus consomme différentes ressources du système :
- du temps processeur ;
- de la mémoire ;
- des descripteurs de fichiers ;
- de l’espace de pile ;
- des processus ou threads ;
- des signaux en attente ;
- des files de messages ;
- des ressources de planification temps réel.
- de mesurer les ressources déjà consommées ;
- de consulter les limites imposées au processus ;
- de modifier certaines de ces limites.
Code: Select all
getrusage()
getrlimit()
setrlimit()
Les fonctions getrlimit() et setrlimit() servent à consulter et modifier les limites de ressources.
1. Mesurer les ressources avec getrusage()
Prototype
Code: Select all
#include <sys/resource.h>
int getrusage(int who, struct rusage *usage);
- 0 en cas de succès ;
- -1 en cas d’erreur, avec errno positionné.
RUSAGE_SELF
Code: Select all
getrusage(RUSAGE_SELF, &usage);
Les statistiques peuvent inclure les ressources consommées par ses threads.
RUSAGE_CHILDREN
Code: Select all
getrusage(RUSAGE_CHILDREN, &usage);
Code: Select all
wait()
waitpid()
waitid()
Un enfant terminé mais non encore récupéré peut également ne pas être pris en compte.
RUSAGE_THREAD
Code: Select all
getrusage(RUSAGE_THREAD, &usage);
Elle est spécifique à Linux et nécessite généralement :
Code: Select all
#define _GNU_SOURCE
2. La structure struct rusage
La fonction getrusage() remplit une structure de type :
Code: Select all
struct rusage
Tous les champs ne sont pas forcément renseignés ou réellement utilisés sur toutes les implémentations UNIX.
Temps CPU utilisateur
Code: Select all
struct timeval ru_utime;
Il correspond au temps utilisé pour exécuter le code normal du programme.
Temps CPU noyau
Code: Select all
struct timeval ru_stime;
Il correspond principalement au temps consommé pendant les appels système.
Structure timeval
Code: Select all
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
};
- tv_sec : nombre de secondes ;
- tv_usec : nombre de microsecondes.
Code: Select all
double secondes =
usage.ru_utime.tv_sec +
usage.ru_utime.tv_usec / 1000000.0;
Code: Select all
long ru_maxrss;
Sous Linux, cette valeur est généralement exprimée en kilooctets.
La mémoire résidente correspond aux pages réellement présentes en mémoire physique.
Défauts de page mineurs
Code: Select all
long ru_minflt;
Exemples :
- page déjà présente dans le cache ;
- allocation paresseuse d’une nouvelle page ;
- copie sur écriture.
Code: Select all
long ru_majflt;
Ils sont généralement plus coûteux que les défauts mineurs.
Opérations d’entrée bloc
Code: Select all
long ru_inblock;
Opérations de sortie bloc
Code: Select all
long ru_oublock;
Changements de contexte volontaires
Code: Select all
long ru_nvcsw;
Un changement volontaire se produit par exemple lorsqu’un thread :
- attend un mutex ;
- appelle sleep() ;
- attend une entrée-sortie ;
- se bloque sur une condition.
Code: Select all
long ru_nivcsw;
Exemples :
- expiration du quantum ;
- préemption par un thread plus prioritaire ;
- décision de l’ordonnanceur.
La structure peut aussi contenir des champs comme :
Code: Select all
ru_ixrss
ru_idrss
ru_isrss
ru_nswap
ru_msgsnd
ru_msgrcv
ru_nsignals
3. Exemple simple avec getrusage()
Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
int main(void)
{
struct rusage usage;
if (getrusage(RUSAGE_SELF, &usage) == -1) {
perror("getrusage");
return EXIT_FAILURE;
}
printf("Temps utilisateur : %ld.%06ld secondes\n",
(long)usage.ru_utime.tv_sec,
(long)usage.ru_utime.tv_usec);
printf("Temps noyau : %ld.%06ld secondes\n",
(long)usage.ru_stime.tv_sec,
(long)usage.ru_stime.tv_usec);
printf("Memoire residente maximale : %ld Ko\n",
usage.ru_maxrss);
printf("Defauts de page mineurs : %ld\n",
usage.ru_minflt);
printf("Defauts de page majeurs : %ld\n",
usage.ru_majflt);
printf("Changements volontaires : %ld\n",
usage.ru_nvcsw);
printf("Changements involontaires : %ld\n",
usage.ru_nivcsw);
return EXIT_SUCCESS;
}
Linux expose également de nombreuses informations dans le pseudo-système de fichiers :
Code: Select all
/proc
Code: Select all
/proc/PID/stat
/proc/PID/status
/proc/PID/limits
Code: Select all
cat /proc/self/status
cat /proc/self/limits
cat /proc/1234/stat
Code: Select all
/proc/PID/limits
L’accès dépend des permissions et de l’identité du processus appelant.
5. Consulter les limites avec getrlimit()
Prototype
Code: Select all
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
- 0 en cas de succès ;
- -1 en cas d’erreur.
Le paramètre rlim pointe vers une structure :
Code: Select all
struct rlimit {
rlim_t rlim_cur;
rlim_t rlim_max;
};
Code: Select all
rlim_cur
Lorsque cette limite est atteinte, le noyau peut :
- faire échouer une opération ;
- envoyer un signal ;
- empêcher une nouvelle allocation ;
- empêcher la création d’une nouvelle ressource.
Code: Select all
rlim_max
Un processus non privilégié peut généralement :
- diminuer sa limite souple ;
- augmenter sa limite souple jusqu’à la limite dure ;
- diminuer sa limite dure.
Un processus privilégié disposant de la capacité :
Code: Select all
CAP_SYS_RESOURCE
RLIM_INFINITY
La valeur :
Code: Select all
RLIM_INFINITY
Exemple :
Code: Select all
if (limit.rlim_cur == RLIM_INFINITY) {
printf("Limite souple illimitee\n");
}
Prototype
Code: Select all
#include <sys/resource.h>
int setrlimit(int resource, const struct rlimit *rlim);
- 0 en cas de succès ;
- -1 en cas d’erreur.
Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
int main(void)
{
struct rlimit limit;
if (getrlimit(RLIMIT_NOFILE, &limit) == -1) {
perror("getrlimit");
return EXIT_FAILURE;
}
printf("Ancienne limite souple : %llu\n",
(unsigned long long)limit.rlim_cur);
printf("Ancienne limite dure : %llu\n",
(unsigned long long)limit.rlim_max);
limit.rlim_cur = 128;
if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
perror("setrlimit");
return EXIT_FAILURE;
}
printf("Nouvelle limite souple : 128\n");
return EXIT_SUCCESS;
}
Code: Select all
rlim_cur <= rlim_max
7. Héritage des limites
Les limites de ressources sont héritées par les processus enfants créés avec :
Code: Select all
fork()
Code: Select all
execve()
Exemple logique :
Code: Select all
fork()
|
+-- enfant
|
+-- setrlimit()
|
+-- execve()
- lancer un programme non fiable ;
- construire un mini-sandbox ;
- limiter un compilateur ;
- limiter un serveur ;
- empêcher une consommation excessive de mémoire ou de CPU.
RLIMIT_AS
Code: Select all
RLIMIT_AS
Cette limite peut affecter :
Code: Select all
malloc()
calloc()
realloc()
mmap()
mremap()
brk()
sbrk()
Code: Select all
ENOMEM
Elle concerne l’ensemble de l’espace virtuel du processus.
RLIMIT_CORE
Code: Select all
RLIMIT_CORE
Pour désactiver les core dumps :
Code: Select all
struct rlimit limit = {0, 0};
setrlimit(RLIMIT_CORE, &limit);
RLIMIT_CPU
Code: Select all
RLIMIT_CPU
Lorsque la limite souple est atteinte, le noyau envoie généralement :
Code: Select all
SIGXCPU
S’il continue à consommer du CPU, le noyau peut ensuite envoyer :
Code: Select all
SIGKILL
Exemple :
Code: Select all
struct rlimit limit;
limit.rlim_cur = 2;
limit.rlim_max = 4;
setrlimit(RLIMIT_CPU, &limit);
- vers 2 secondes de CPU, le processus reçoit SIGXCPU ;
- vers 4 secondes de CPU, il peut être tué.
RLIMIT_DATA
Code: Select all
RLIMIT_DATA
Cette limite peut notamment affecter :
Code: Select all
brk()
sbrk()
malloc()
RLIMIT_FSIZE
Code: Select all
RLIMIT_FSIZE
Si le processus dépasse cette limite, il peut recevoir :
Code: Select all
SIGXFSZ
Exemple :
Code: Select all
struct rlimit limit = {
.rlim_cur = 1024,
.rlim_max = 1024
};
setrlimit(RLIMIT_FSIZE, &limit);
RLIMIT_MEMLOCK
Code: Select all
RLIMIT_MEMLOCK
Fonctions concernées :
Code: Select all
mlock()
mlockall()
munlock()
munlockall()
mmap()
shmctl()
Cette fonctionnalité est utile pour :
- les programmes temps réel ;
- les données cryptographiques sensibles ;
- les programmes devant éviter les défauts de page.
Code: Select all
RLIMIT_MSGQUEUE
API associées :
Code: Select all
mq_open()
mq_send()
mq_receive()
mq_close()
mq_unlink()
RLIMIT_NICE
Code: Select all
RLIMIT_NICE
API associées :
Code: Select all
nice()
getpriority()
setpriority()
RLIMIT_NOFILE
Code: Select all
RLIMIT_NOFILE
Cette limite concerne notamment :
- les fichiers ;
- les sockets ;
- les pipes ;
- les événements comme eventfd ;
- les descripteurs epoll ;
- les descripteurs inotify.
Code: Select all
EMFILE
Code: Select all
struct rlimit limit = {
.rlim_cur = 32,
.rlim_max = 32
};
setrlimit(RLIMIT_NOFILE, &limit);
RLIMIT_NPROC
Code: Select all
RLIMIT_NPROC
Lorsque la limite est atteinte :
Code: Select all
fork()
clone()
pthread_create()
Code: Select all
EAGAIN
Si l’utilisateur possède déjà beaucoup de processus, une limite faible peut être atteinte immédiatement.
RLIMIT_RSS
Code: Select all
RLIMIT_RSS
Sous Linux, elle est historiquement peu appliquée ou ignorée dans de nombreux cas.
Il ne faut donc pas compter dessus pour construire une limitation mémoire fiable.
RLIMIT_RTPRIO
Code: Select all
RLIMIT_RTPRIO
API associées :
Code: Select all
sched_setscheduler()
sched_setparam()
pthread_setschedparam()
Code: Select all
SCHED_FIFO
SCHED_RR
Code: Select all
RLIMIT_RTTIME
La valeur est exprimée en microsecondes.
Lorsque la limite souple est atteinte, le processus peut recevoir :
Code: Select all
SIGXCPU
Code: Select all
SIGKILL
Code: Select all
RLIMIT_SIGPENDING
Elle concerne principalement les signaux envoyés avec :
Code: Select all
sigqueue()
RLIMIT_STACK
Code: Select all
RLIMIT_STACK
Un dépassement de pile peut provoquer :
Code: Select all
SIGSEGV
Code: Select all
sigaltstack()
Code: Select all
sigaction()
Code: Select all
SA_ONSTACK
9. Afficher une limite proprement
Le type :
Code: Select all
rlim_t
Pour l’afficher, on peut effectuer une conversion explicite.
Code: Select all
#include <stdio.h>
#include <sys/resource.h>
static void afficher_limite(const char *nom, int resource)
{
struct rlimit limit;
if (getrlimit(resource, &limit) == -1) {
perror("getrlimit");
return;
}
printf("%s\n", nom);
if (limit.rlim_cur == RLIM_INFINITY) {
printf(" Souple : illimitee\n");
} else {
printf(" Souple : %llu\n",
(unsigned long long)limit.rlim_cur);
}
if (limit.rlim_max == RLIM_INFINITY) {
printf(" Dure : illimitee\n");
} else {
printf(" Dure : %llu\n",
(unsigned long long)limit.rlim_max);
}
}
Code: Select all
int main(void)
{
afficher_limite("Descripteurs", RLIMIT_NOFILE);
afficher_limite("Pile", RLIMIT_STACK);
afficher_limite("Memoire virtuelle", RLIMIT_AS);
return 0;
}
Code: Select all
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void)
{
struct rlimit limit;
if (getrlimit(RLIMIT_NPROC, &limit) == -1) {
perror("getrlimit");
return EXIT_FAILURE;
}
printf("Ancienne limite souple : %llu\n",
(unsigned long long)limit.rlim_cur);
printf("Ancienne limite dure : %llu\n",
(unsigned long long)limit.rlim_max);
limit.rlim_cur = 30;
if (limit.rlim_max < limit.rlim_cur) {
limit.rlim_cur = limit.rlim_max;
}
if (setrlimit(RLIMIT_NPROC, &limit) == -1) {
perror("setrlimit");
return EXIT_FAILURE;
}
for (int i = 0; i < 100; i++) {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
break;
}
if (pid == 0) {
sleep(10);
_exit(EXIT_SUCCESS);
}
printf("Enfant cree : PID=%ld\n", (long)pid);
}
while (wait(NULL) > 0) {
}
if (errno != ECHILD) {
perror("wait");
}
return EXIT_SUCCESS;
}
Cependant, RLIMIT_NPROC prend en compte tous les processus ou threads appartenant à l’utilisateur réel.
11. Valeurs non représentables
Sur certaines anciennes implémentations, le type utilisé par le noyau pour stocker une limite pouvait être plus grand que le type rlim_t visible par l’application.
Une limite pouvait alors être impossible à représenter exactement.
Certaines implémentations définissent :
Code: Select all
RLIM_SAVED_CUR
RLIM_SAVED_MAX
Sur les systèmes Linux modernes 64 bits, ce problème est rarement rencontré.
12. Erreurs courantes
EINVAL
Peut survenir lorsque :
- la ressource est invalide ;
- la limite souple est supérieure à la limite dure ;
- une valeur fournie est incohérente.
Peut survenir lorsqu’un processus non privilégié tente :
- d’augmenter une limite dure ;
- de dépasser la limite autorisée par le système ;
- de modifier une ressource nécessitant des privilèges.
Peut survenir si le pointeur vers la structure est invalide.
ENOMEM
Peut être retournée par une allocation mémoire lorsqu’une limite comme RLIMIT_AS ou RLIMIT_DATA est atteinte.
EMFILE
Indique que le processus a atteint sa limite de descripteurs de fichiers.
EAGAIN
Peut indiquer que la limite de processus ou de threads a été atteinte.
13. Équivalent avec le shell
Le shell fournit généralement la commande :
Code: Select all
ulimit
Code: Select all
ulimit -a
Code: Select all
ulimit -n 128
Code: Select all
ulimit -c 0
Code: Select all
ulimit -t 5
Les limites définies dans le shell sont héritées par les programmes lancés depuis celui-ci.
14. Différence entre limites de ressources et priorités
Une limite de ressources indique la quantité maximale qu’un processus peut consommer.
Une priorité indique plutôt dans quel ordre le processus doit être exécuté par rapport aux autres.
Exemples de priorités :
Code: Select all
nice()
setpriority()
sched_setscheduler()
pthread_setschedparam()
Code: Select all
RLIMIT_CPU
RLIMIT_AS
RLIMIT_NOFILE
RLIMIT_NPROC
15. Comparaison avec Windows
Sous Windows, les Job Objects remplissent un rôle partiellement comparable.
Ils permettent notamment de limiter :
- la mémoire ;
- le temps CPU ;
- le nombre de processus ;
- certains comportements des processus enfants.
Code: Select all
CreateJobObject()
SetInformationJobObject()
AssignProcessToJobObject()
QueryInformationJobObject()
Code: Select all
JOBOBJECT_EXTENDED_LIMIT_INFORMATION
JOBOBJECT_BASIC_LIMIT_INFORMATION
JOBOBJECT_CPU_RATE_CONTROL_INFORMATION
Pour des limitations plus avancées portant sur un groupe de processus, Linux propose également les cgroups.
16. Exemple complet : exécuter une commande avec des limites
Code: Select all
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <unistd.h>
static int fixer_limite(int resource,
rlim_t soft,
rlim_t hard)
{
struct rlimit limit;
limit.rlim_cur = soft;
limit.rlim_max = hard;
if (setrlimit(resource, &limit) == -1) {
perror("setrlimit");
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr,
"Utilisation : %s commande [arguments...]\n",
argv[0]);
return EXIT_FAILURE;
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return EXIT_FAILURE;
}
if (pid == 0) {
if (fixer_limite(RLIMIT_CPU, 2, 3) == -1) {
_exit(EXIT_FAILURE);
}
if (fixer_limite(RLIMIT_NOFILE, 32, 32) == -1) {
_exit(EXIT_FAILURE);
}
if (fixer_limite(RLIMIT_AS,
256ULL * 1024 * 1024,
256ULL * 1024 * 1024) == -1) {
_exit(EXIT_FAILURE);
}
execvp(argv[1], &argv[1]);
perror("execvp");
_exit(EXIT_FAILURE);
}
int status;
if (waitpid(pid, &status, 0) == -1) {
perror("waitpid");
return EXIT_FAILURE;
}
if (WIFEXITED(status)) {
printf("Code de sortie : %d\n",
WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Processus termine par le signal : %d\n",
WTERMSIG(status));
}
return EXIT_SUCCESS;
}
- crée un processus enfant ;
- limite son temps CPU ;
- limite ses descripteurs ;
- limite son espace mémoire virtuel ;
- exécute la commande demandée ;
- attend sa terminaison.
Mesure des ressources
Code: Select all
getrusage()
Code: Select all
getrlimit()
Code: Select all
setrlimit()
Code: Select all
fork()
execve()
execvp()
wait()
waitpid()
waitid()
Code: Select all
sigaction()
sigaltstack()
sigqueue()
Code: Select all
nice()
getpriority()
setpriority()
sched_setscheduler()
sched_setparam()
pthread_setschedparam()
Code: Select all
malloc()
calloc()
realloc()
brk()
sbrk()
mmap()
mremap()
mlock()
mlockall()
munlock()
munlockall()
Code: Select all
mq_open()
mq_send()
mq_receive()
mq_close()
mq_unlink()
Code: Select all
struct rusage
struct rlimit
struct timeval
Sélection de la cible de getrusage()
Code: Select all
RUSAGE_SELF
RUSAGE_CHILDREN
RUSAGE_THREAD
Code: Select all
RLIM_INFINITY
RLIM_SAVED_CUR
RLIM_SAVED_MAX
Code: Select all
RLIMIT_AS
RLIMIT_CORE
RLIMIT_CPU
RLIMIT_DATA
RLIMIT_FSIZE
RLIMIT_MEMLOCK
RLIMIT_MSGQUEUE
RLIMIT_NICE
RLIMIT_NOFILE
RLIMIT_NPROC
RLIMIT_RSS
RLIMIT_RTPRIO
RLIMIT_RTTIME
RLIMIT_SIGPENDING
RLIMIT_STACK
Code: Select all
SIGXCPU
SIGXFSZ
SIGSEGV
SIGKILL
La fonction :
Code: Select all
getrusage()
- le processus courant ;
- ses enfants terminés ;
- le thread courant sous Linux.
- le temps CPU utilisateur ;
- le temps CPU noyau ;
- la mémoire résidente maximale ;
- les défauts de page ;
- les opérations bloc ;
- les changements de contexte.
Code: Select all
getrlimit()
La fonction :
Code: Select all
setrlimit()
La limite souple est la limite actuellement appliquée.
La limite dure est le plafond maximal de la limite souple.
Les limites sont héritées par fork() et conservées après execve().
Les limites les plus utiles sont souvent :
Code: Select all
RLIMIT_CPU
RLIMIT_AS
RLIMIT_NOFILE
RLIMIT_NPROC
RLIMIT_STACK
RLIMIT_CORE
RLIMIT_FSIZE
