C++ system() вызывает 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;
}
Это должно быть скомпилировано с g++ -ggdb -std=c++11
(g++ 4.9 на Debian). Обратите внимание, что int *watch_errno
полезно только для того, чтобы позволить GDB смотреть errno
,
Когда он запускается под gdb
Я получаю это:
(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]
По какой-то причине errno
установлен в ENOMEM
в строке 9, которая соответствуетsystem()
вызов. Обратите внимание, что если вектор имеет меньший размер (я думаю, это зависит от того, на каком компьютере вы будете запускать код), код работает нормально иsystem(NULL)
возвращает 1, как и должно быть, когда доступна оболочка.
Почему флаг ENOMEM
поднял? Почему код не использует память подкачки? Это ошибка? Есть ли обходной путь? Было бы popen
или же exec*
делать то же самое? (Я знаю, я должен был задавать только один вопрос на пост, но все эти вопросы можно было бы обобщить, "что происходит?")
Как и просили, вот результат 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
а здесь соответствующая часть 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
вот вывод free:
total used free shared buffers cached
Mem: 7915060 1668928 6246132 49576 34668 1135612
-/+ buffers/cache: 498648 7416412
Swap: 2928636 0 2928636
2 ответа
system()
Функция работает, сначала создав новую копию процесса с fork()
или аналогичный (в Linux это заканчивается clone()
системный вызов, как вы показываете), а затем, в дочернем процессе, вызов exec
создать оболочку, запустив нужную команду.
fork()
Вызов может потерпеть неудачу, если для нового процесса недостаточно виртуальной памяти (даже если вы намереваетесь немедленно заменить ее на гораздо меньшую площадь, ядро не может этого знать). Некоторые системы позволяют вам торговать возможностью форкировать большие процессы для уменьшения гарантий, что сбой страниц может завершиться с копированием при записи (vfork()
) или переполнение памяти (/proc/sys/vm/overcommit_memory
а также /proc/sys/vm/overcommit_ratio
).
Обратите внимание, что вышесказанное в равной степени относится к любой библиотечной функции, которая может создавать новые процессы, например popen()
, Хотя и не exec()
, поскольку это заменяет процесс и не клонирует его.
Если предоставленные механизмы не подходят для вашего варианта использования, вам может потребоваться реализовать свой собственный system()
замена. Я рекомендую запустить дочерний процесс на ранней стадии (до того, как вы выделите много памяти), единственной задачей которого является принятие NUL
разделенные командные строки на stdin
и сообщить о выходе stdout
,
Схема последнего решения в псевдокоде выглядит примерно так:
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;
}
Вы захотите добавить соответствующие проверки ошибок повсюду, ссылаясь на справочные страницы. Возможно, вы захотите сделать его более объектно-ориентированным и, возможно, использовать iostreams для операций чтения и записи и т. Д.
Ваша линия
std::vector<double> a(7e8,1);
вероятно неправильно Вы вызываете конструктор для std::vector, который принимает размер вектора и инициализирующий элемент. 7e8 преобразуется в огромный размер (т.е. до 700000000 элементов).
Возможно, вы захотите построить двухэлементный вектор, поэтому используйте
std::vector<double> a{7e8,1};
А с вашим огромным вектором библиотечная функция system(3) будет вызывать системный вызов fork(2), который завершается с ошибкой:
ENOMEM
fork()
не удалось выделить необходимые структуры ядра из-за нехватки памяти.
Возможно, вы достигли некоторого предела, например, установленного setrlimit (2) где-то еще.
Пытаться cat /proc/self/limits
найти их (в Linux).
Используйте strace (1) (например, как strace -f yourprogram
) выяснить, что происходит; осмотреться fork
или же clone
линия...
Кстати, система (3) должна вернуть код ошибки при сбое. Вы должны проверить это. И вы можете позвонить system("echo here pid $$");
вместо system(NULL);