2015-10-01 1 views
0

Cette question est un M(not)WE de cette question. J'ai écrit un code qui reproduit l'erreur:C++ système() soulève ENOMEM

#include <cstdlib> 
#include <iostream> 
#include <vector> 

int *watch_errno = __errno_location(); 

int main(){ 
    std::vector<double> a(7e8,1); // allocate a big chunk of memory 
    std::cout<<system(NULL)<<std::endl; 
} 

Il doit être compilé avec g++ -ggdb -std=c++11 (g ++ 4.9 sur Debian). Notez que le int *watch_errno est utile uniquement pour permettre à gdb de regarder errno.

Lorsqu'il est exécuté sous gdb, je reçois ceci:

(gdb) watch *watch_errno 
Hardware watchpoint 1: *watch_errno 
(gdb) r 
Starting program: /tmp/bug 
Hardware watchpoint 1: *watch_errno 

Old value = <unreadable> 
New value = 0 
__static_initialization_and_destruction_0 (__initialize_p=1, __priority=65535) at bug.cpp:10 
10  } 
(gdb) c 
Continuing. 
Hardware watchpoint 1: *watch_errno 

Old value = 0 
New value = 12 
0x00007ffff7252421 in do_system ([email protected]=0x7ffff7372168 "exit 0") at ../sysdeps/posix/system.c:116 
116  ../sysdeps/posix/system.c: No such file or directory. 
(gdb) bt 
#0 0x00007ffff7252421 in do_system ([email protected]=0x7ffff7372168 "exit 0") at ../sysdeps/posix/system.c:116 
#1 0x00007ffff7252510 in __libc_system (line=<optimized out>) at ../sysdeps/posix/system.c:182 
#2 0x0000000000400ad8 in main() at bug.cpp:9 
(gdb) l 
111  in ../sysdeps/posix/system.c 
(gdb) c 
Continuing. 
0 
[Inferior 1 (process 5210) exited normally] 

Pour une raison quelconque errno est réglé sur ENOMEM à la ligne 9, qui correspond à la system() appel. Notez que si le vecteur a une taille plus petite (je suppose que dépend de l'ordinateur sur lequel vous allez exécuter le code), le code fonctionne bien et system(NULL) renvoie 1 comme il se doit lorsqu'un shell est disponible.

Pourquoi le drapeau ENOMEM est-il surélevé? Pourquoi le code n'utilise-t-il pas la mémoire d'échange? Est-ce un bug? Y at-il un travail autour? Est-ce que popen ou exec* feraient la même chose? (Je sais, je ne devrais poser une question par courrier, mais toutes ces questions pourraient se résumer par « ce qui se passe? »)

Comme demandé, voici le résultat de ulimit -a:

-t: cpu time (seconds)    unlimited 
-f: file size (blocks)    unlimited 
-d: data seg size (kbytes)   unlimited 
-s: stack size (kbytes)    8192 
-c: core file size (blocks)   0 
-m: resident set size (kbytes)  unlimited 
-u: processes      30852 
-n: file descriptors    65536 
-l: locked-in-memory size (kbytes) 64 
-v: address space (kbytes)   unlimited 
-x: file locks      unlimited 
-i: pending signals     30852 
-q: bytes in POSIX msg queues  819200 
-e: max nice      0 
-r: max rt priority     0 
-N 15:        unlimited 

et ici la partie pertinente de strace -f myprog

mmap(NULL, 5600002048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7faa98562000 
rt_sigaction(SIGINT, {SIG_IGN, [], SA_RESTORER, 0x7fabe622b180}, {SIG_DFL, [], 0}, 8) = 0 
rt_sigaction(SIGQUIT, {SIG_IGN, [], SA_RESTORER, 0x7fabe622b180}, {SIG_DFL, [], 0}, 8) = 0 
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 
clone(child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0x7fff8797635c) = -1 ENOMEM (Cannot allocate memory) 
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7fabe622b180}, NULL, 8) = 0 
rt_sigaction(SIGQUIT, {SIG_DFL, [], SA_RESTORER, 0x7fabe622b180}, NULL, 8) = 0 
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0 
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fabe6fde000 
write(1, "0\n", 20 
)      = 2 
write(1, "8\n", 28 
)      = 2 
munmap(0x7faa98562000, 5600002048)  = 0 

est ici la sortie du libre:

  total  used  free  shared buffers  cached 
Mem:  7915060 1668928 6246132  49576  34668 1135612 
-/+ buffers/cache:  498648 7416412 
Swap:  2928636   0 2928636 
+0

Vous devriez utiliser [strace (1)] (http://man7.org/linux/man- pages/man1/strace.1.html) sur votre programme. –

+2

Le titre est un peu trompeur, car la question est liée à l'appel de la fonction 'system()', qui est différente d'un _system call_. – Michael

+0

Est-ce sur une plate-forme 64 bits? –

Répondre

2

La fonction system() fonctionne en créant d'abord une nouvelle copie du processus avec fork() ou similaire (sous Linux, cela se termine dans l'appel système clone(), comme vous le montrer) puis , dans le processus fils, appelez exec pour créer un shell exécutant la commande souhaitée. L'appel fork() peut échouer si la mémoire virtuelle est insuffisante pour le nouveau processus (même si vous avez l'intention de le remplacer immédiatement par une empreinte beaucoup plus petite, le noyau ne peut pas le savoir). Certains systèmes vous permettent d'échanger la capacité de débiter de grands processus pour des garanties réduites que les défauts de page peuvent échouer, avec une copie sur écriture (vfork()) ou une surcharge de mémoire (/proc/sys/vm/overcommit_memory et /proc/sys/vm/overcommit_ratio). Notez que ce qui précède s'applique également à toute fonction de bibliothèque qui peut créer de nouveaux processus - par ex. popen(). Bien que pas exec(), que remplace le processus et ne le clone pas.

Si les mécanismes fournis ne sont pas adaptés à votre cas d'utilisation, vous devrez peut-être implémenter votre propre remplacement system(). Je recommande de démarrer un processus enfant dès le début (avant d'allouer beaucoup de mémoire) dont le seul travail consiste à accepter NUL lignes de commande séparées sur stdin et signaler l'état de sortie sur stdout.

Un aperçu de la dernière solution en pseudo-code ressemble à quelque chose comme:

int request_fd[2]; 
int reply_fd[2]; 

pipe(request_fd); 
pipe(reply_fd); 

if (fork()) { 
    /* in parent */ 
    close(request_fd[0]); 
    close(reply_fd[1]); 
} else { 
    /* in child */ 
    close(request_fd[1]); 
    close(reply_fd[0]); 
    while (read(request_fd[0], command)) { 
     int result = system(command); 
     write(reply_fd[1], result); 
    } 
    exit(); 
} 

// Important: don't allocate until after the fork() 
std::vector<double> a(7e8,1); // allocate a big chunk of memory 

int my_system_replacement(const char* command) { 
    write(request_fd[1], command); 
    read(reply_fd[0], result); 
    return result; 
} 

Vous voulez ajouter des contrôles d'erreur appropriés tout au long, en se référant aux pages de manuel. Et vous pourriez vouloir le rendre plus orienté objet, et peut-être utiliser des iostreams pour vos opérations de lecture et d'écriture, etc.

+0

thx pour l'édition et répondre à ma question. Si je comprends ce que vous dites, dès que la RAM est pleine à plus de 50%, l'appel 'system' ne fonctionnera pas car il va d'abord essayer de copier le processus et sa mémoire allouée. Vous me donnez deux solutions: écrire ma propre commande système (pouvez-vous indiquer où je pourrais trouver des informations sur la façon de faire cela) ou utiliser 'exec()'. – PinkFloyd

+1

La 'fourchette' échouera probablement lorsque le processus est plus grand que la mémoire libre _virtual_ libre (RAM + swap). 'exec' est le bon choix si c'est vraiment la dernière chose que vous voulez faire au lieu de quitter, sinon vous avez le choix d'ajuster les paramètres ajustables 'overcommit' (si supportés sur votre système) ou d'implémenter un processus worker worker. Je vais mettre à jour ma réponse avec un croquis de cette dernière solution. –

0

Votre ligne

std::vector<double> a(7e8,1); 

est probablement faux. Vous appelez un constructeur pour std::vector qui prend une taille de vecteur et un élément d'initialisation. Le 7e8 est converti en une taille énorme (c'est-à-dire en 700000000 éléments).

Vous pouvez construire un vecteur à deux éléments, alors utilisez

std::vector<double> a{7e8,1}; 

Et avec votre vecteur énorme, la fonction de la bibliothèque system(3) appellera fork(2) appel système qui échoue avec:

ENOMEM fork() failed to allocate the necessary kernel structures because memory is tight.

Peut-être avez-vous atteint une limite, par exemple fixé par setrlimit(2) ailleurs.
Essayez cat /proc/self/limits pour les trouver (sur Linux).

Utilisez strace(1) (par exemple strace -f yourprogram) pour savoir ce qui se passe; regardez autour de la ligne fork ou clone ...

BTW, system(3) doit renvoyer un code d'erreur en cas d'échec. Vous devriez le tester.Et vous pouvez appeler system("echo here pid $$"); au lieu de system(NULL);

+0

Non, c'est le point de ma question ... Je crée un énorme vecteur pour avoir beaucoup de mémoire allouée. Cela sera la cause du drapeau 'ENOMEM' quand' system() 'est appelé. Pour les petits vecteurs, le code fonctionne bien. – PinkFloyd

+1

Si mon téléphone/tablette/lave-vaisselle n'avait pas 5 Go de mémoire disponible pour l'allocation, je serais assez contrarié, cependant. –

+0

@KerrekSB: votre * lave-vaisselle * a 5Gbyte? Le mien a probablement un microcontrôleur 8 bits bon marché (donc sûrement moins de 1 mégaoctet) et il lave les plats au besoin. –