sistema c++() plantea ENOMEM
c++11 gdb (2)
Esta pregunta es M (no) NOSOTROS de esta pregunta . Escribí un código que reproduce el error:
#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;
}
Tiene que compilarse con g++ -ggdb -std=c++11
(g ++ 4.9 en un Debian). Tenga en cuenta que int *watch_errno
solo es útil para permitir que gdb vea errno
.
Cuando se ejecuta bajo gdb
, obtengo esto:
(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 (line=line@entry=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 (line=line@entry=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]
Por alguna razón, errno
se establece en ENOMEM
en la línea 9 que corresponde a la llamada al system()
. Tenga en cuenta que si el vector tiene un tamaño más pequeño (supongo que depende de qué computadora ejecutará el código), el código funciona bien y el system(NULL)
devuelve 1 como debería cuando hay un shell disponible.
¿Por qué se levanta la bandera ENOMEM
? ¿Por qué el código no está usando la memoria de intercambio? ¿Es esto un error? ¿Hay alguna solución? ¿ popen
o no hacer lo mismo? (Lo sé, solo debería hacer una pregunta por publicación, pero todas estas preguntas podrían resumirse en "¿qué está pasando?")
Según lo solicitado, aquí está el resultado 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
y aquí la parte relevante 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
aquí está la salida de gratis:
total used free shared buffers cached
Mem: 7915060 1668928 6246132 49576 34668 1135612
-/+ buffers/cache: 498648 7416412
Swap: 2928636 0 2928636
Tu linea
std::vector<double> a(7e8,1);
es probablemente incorrecto Está llamando a un constructor para std :: vector que toma un tamaño de vector y un elemento de inicialización. El 7e8 se convierte a un tamaño enorme (es decir, a 700000000 elementos).
Es posible que desee construir un vector de dos elementos, así que use
std::vector<double> a{7e8,1};
Y con su gran vector, la función de biblioteca del sistema (3) llamará a fork (2) llamada al sistema que está fallando con:
ENOMEM
fork()
no pudo asignar las estructuras de kernel necesarias porque la memoria es estrecha.
Quizás hayas llegado a algún límite, por ejemplo, establecido por setrlimit (2) en otro lugar.
Pruebe cat /proc/self/limits
para encontrarlos (en Linux).
Use strace (1) (p. Ej. Como strace -f yourprogram
) para descubrir qué está sucediendo; mira alrededor de la línea de fork
o clone
...
Por cierto, el sistema (3) debería devolver un código de error cuando falla. Debes probarlo. Y es posible que desee llamar al system("echo here pid $$");
en lugar de system(NULL);
La función system()
funciona creando primero una nueva copia del proceso con fork()
o similar (en Linux, esto termina en la llamada al sistema clone()
, como se muestra) y luego, en el proceso secundario, llama al exec
para crear un shell ejecutando el comando deseado.
La llamada fork()
puede fallar si no hay memoria virtual suficiente para el nuevo proceso (aunque intente reemplazarlo inmediatamente con una huella mucho más pequeña, el kernel no puede saberlo). Algunos sistemas le permiten intercambiar la capacidad de bifurcar procesos grandes para obtener garantías reducidas de que fallas de página pueden fallar, con copy-on-write ( vfork()
) o vfork()
memoria ( /proc/sys/vm/overcommit_memory
y /proc/sys/vm/overcommit_ratio
).
Tenga en cuenta que lo anterior se aplica por igual a cualquier función de biblioteca que pueda crear nuevos procesos, por ejemplo, popen()
. Aunque no es exec()
, ya que eso reemplaza el proceso y no lo clona.
Si los mecanismos provistos son inadecuados para su caso de uso, entonces puede necesitar implementar su propio reemplazo de system()
. Recomiendo comenzar un proceso hijo desde el principio (antes de asignar mucha memoria) cuyo único trabajo es aceptar líneas de comando separadas por NUL
en stdin
e informar el estado de salida en stdout
.
Un esquema de la última solución en pseudo-código se ve algo así como:
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;
}
Deberá agregar comprobaciones de error apropiadas en todo momento, por referencia a las páginas man. Y es posible que desee hacerlo más orientado a objetos, y tal vez usar iostreams para sus operaciones de lectura y escritura, etc.