2015-12-08 1 views
4

Tenir compte du programme suivant:malloc errno réglage EAGAIN

#include <sys/mman.h>               
#include <stdlib.h>                
#include <errno.h>                

int                    
main()                   
{                    
    errno = 0; 
    mlockall(MCL_FUTURE);           
    char *a = malloc(1);              
    if (!a)                  
    exit(errno);                 
    munlockall();                 
    exit(0);                  
} 

Lors de l'exécution en tant qu'utilisateur normal je reçois:

~ ./a.out                
~ echo $?                
11 

De /usr/include/asm-generic/errno-base.h:

#define EAGAIN 11 /* Try again */          

Lors de l'exécution comme root ou au passage MCL_FUTURE | MCL_CURRENT il fonctionne avec succès. J'ai supposé que les permissions étaient insuffisantes ou que les drapeaux étaient faux, mais ni EPERM ni EINVAL n'ont été retournés.

Cette erreur n'est pas spécifiée dans la page de manuel des fonctions ni dans la spécification POSIX pour mlockall. Placer un printf après mlockall révèle que c'est malloc qui définit errno.

Et semble encore plus étrange, malloc ne pas définir EAGAIN (ou je suis à la recherche dans le mauvais endroit):

/usr/src/glibc/glibc-2.19/malloc grep -r . -e EAGAIN 

Alors quel est le problème?

~ uname -r                                                 18:15:04 
3.16-2-486 
~ gcc --version                                               18:15:05 
gcc (Debian 4.9.2-10) 4.9.2 
Copyright (C) 2014 Free Software Foundation, Inc. 
This is free software; see the source for copying conditions. There is NO 
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

~ ldd --version                                               18:15:11 
ldd (Debian GLIBC 2.19-18+deb8u1) 2.19 
Copyright (C) 2014 Free Software Foundation, Inc. 
This is free software; see the source for copying conditions. There is NO 
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
Written by Roland McGrath and Ulrich Drepper. 
~                                                   18:15:15 
+5

Vous définissez 'errno' sur 0, en appelant * deux * fonctions, puis en vérifiant la valeur de' errno'. Vous ne pouvez pas dire si elle a été définie par 'mlockall' ou par' malloc'. Vous mentionnez d'ajouter un appel 'printf', mais ce n'est pas dans le code que vous avez posté. Vous devriez mettre 'errno' à 0 avant chaque appel et le vérifier immédiatement après chaque appel. –

+0

Quel répertoire se trouve 'a.out'? –

+1

De la page de manuel _If MCL_FUTURE a été spécifié, un appel système ultérieur (par exemple mmap (2), sbrk (2), malloc (3)) peut échouer si le nombre d'octets verrouillés dépasse le nombre autorisé maximum (voir ci-dessous).Dans les mêmes circonstances, la croissance de la pile peut également échouer: le noyau refusera l'expansion de la pile et délivrera un signal SIGSEGV au processus. – alvits

Répondre

4

Votre appel mlockall() demande toutes les futures allocations de mémoire à verrouiller. Cependant, le système d'exploitation définit une quantité maximale de mémoire qui peut être verrouillée par n'importe quel processus non privilégié. Vous pouvez interroger ce montant avec getrlimit(RLIMIT_MEMLOCK,...). Sur mon système, c'est 65536 octets.

Maintenant, quand j'exécuter votre programme sur mon système, en utilisant strace(1) pour voir ce que les appels système sont fait, je reçois le texte suivant:

mlockall(MCL_FUTURE)     = 0 
brk(0)         = 0x2318000 
brk(0x2339000)       = 0x2318000 
mmap(NULL, 1048576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 EAGAIN (Resource temporarily unavailable) 
mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 EAGAIN (Resource temporarily unavailable) 
mmap(NULL, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 EAGAIN (Resource temporarily unavailable) 
mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 EAGAIN (Resource temporarily unavailable) 
mmap(NULL, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 EAGAIN (Resource temporarily unavailable) 
exit_group(11)       = ? 

Alors malloc premier utilise brk pour essayer d'allouer 135168 octets (0x2339000-0x2318000) . Cela échoue car il dépasse la limite de verrouillage, et donc brk laisse inchangé le "point de rupture" (le sommet du segment de données du processus). (Voir la note à la page de manuel brk(2) sur les conventions différentes entre la bibliothèque C et les versions du noyau de brk().)

malloc tente alors au lieu d'allouer 1048576 octets en utilisant mmap. Cela échoue également (car il dépasse 65536 octets), et nous voyons ici le code d'erreur EAGAIN étant retourné. La page de manuel pour les documents mmap(2)errno est définie sur EAGAIN si "le fichier a été verrouillé ou si la mémoire a été verrouillée", ce qui est exactement le cas ici. malloc, comme de nombreuses fonctions de la bibliothèque, passera par la valeur errno laissée par les appels système qu'il fait, donc EAGAIN est ce que vous voyez lorsque malloc renvoie.

(Les appels mmap supplémentaires avec PROT_NONE semblent être destinées à réserver un espace d'adresse pour une utilisation ultérieure, et faire en sorte que les allocations futures soient alignées de manière appropriée. Voir malloc/arena.c dans la source de glibc pour plus de détails gores. Ils échouent aussi dans ce cas, mais ce n'est pas si important.)

Donc, en bref, le problème est que malloc essaie de demander au système d'exploitation une quantité de mémoire beaucoup plus importante que vous, l'utilisateur, demandé. C'est pour l'efficacité, puisque dans la plupart des cas vous allez allouer plus de petits morceaux de mémoire, et vous ne voulez pas faire un appel système pour chacun. Mais ce montant dépasse la limite de la mémoire verrouillée, donc il échoue. EAGAIN est le code d'erreur défini par l'appel système mmap dans ce cas.

Peut-être la page de manuel malloc devrait mentionner ce paramètre errno possible, mais il est assez fréquent que les fonctions de bibliothèque de niveau supérieur ne décrivent pas toutes les façons possibles errno pourraient être réglés par les appels système sous-jacent. (Par exemple, fprintf(3) appels write(2), ce qui pourrait définir errno à ENOSPC si le disque est plein, mais vous ne trouverez aucune mention de cela dans la page de manuel fprintf(3).) Vous êtes juste censé savoir. Si vous souhaitez utiliser mlockall(MCL_FUTURE), vous ne pouvez probablement pas prévoir d'allouer de la mémoire en utilisant malloc(3). Vous devrez l'obtenir manuellement de sbrk(2) ou mmap(2), et bien sûr, prévoyez de le garder sous la limite appropriée ou échouer gracieusement. Ceci est très gênant et restrictif, donc si vous avez besoin d'un peu de mémoire verrouillée, et que vous n'êtes pas root, vous voulez probablement simplement utiliser mlock(2) sur des objets suffisamment petits à la place.

+1

En fait, C99 dit explicitement (7.5): _La valeur de errno peut être non nulle par un appel de fonction de bibliothèque, qu'il y ait ou non une erreur, à condition que l'utilisation de errno ne soit pas documentée dans la description de la fonction - en d'autres termes, 'errno' peut être défini arbitrairement par une fonction de bibliothèque standard tant que la norme n'impose aucune exigence spécifique, ce qui n'est pas le cas pour' fprintf' ou 'malloc' parmi beaucoup d'autres. Je suis sûr que les variantes plus modernes de la norme ont le même libellé ou une formulation similaire. – davmac

+0

@davmac: En effet, et comme je l'ai commenté plus haut, de même [POSIX (2.3)] (http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_03.html) dit: "Les implémentations ne doivent pas générer une erreur différente nombre de ceux décrits ici pour les conditions d'erreur décrites dans ce volume de IEEE Std 1003.1-2001, mais peuvent générer des erreurs supplémentaires, sauf si explicitement interdit pour une fonction particulière. " –

0
  1. mlockall est réglage du numéro d'erreur, la raison partie ou la totalité de la plage d'adresses spécifiée ne pouvait pas être verrouillée.
  2. La norme malloc() ne définit pas d'erreur EAGAIN en cas d'erreur.

Man page

mlockall

malloc

0

Quelle est la valeur de retour de mlockall()?

par the POSIX standard:

[EAGAIN]

partie ou la totalité de la mémoire identifiée par l'opération n'a pas pu être verrouillée lorsque l'appel a été fait.

par le Linux man page:

EAGAIN Some or all of the specified address range could not be 
      locked. 
+0

Comme je l'ai noté ci-dessus, au moins dans mes tests, 'mlockall()' réussit réellement, et ne définit pas du tout d'errno. L'OP ne l'a pas expliqué très clairement, mais en fait c'est 'malloc' (ou plutôt' mmap', qu'il appelle) qui définit 'errno = EAGAIN'. –