Этот вопрос М (не) мы этого вопрос. Я написал код, который воспроизводит ошибку:
#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
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);
вероятно неправильно Вы вызываете конструктор для станд :: вектор который принимает размер вектора и инициализирующий элемент. 7e8 преобразуется в огромный размер (то есть в 700000000 элементов).
Возможно, вы захотите построить двухэлементный вектор, поэтому используйте
std::vector<double> a{7e8,1};
И с вашим огромным вектором, Система (3) вызовет библиотечную функцию вилка (2) системный вызов, который терпит неудачу с:
ENOMEM
fork()
не удалось выделить необходимые структуры ядра
потому что память тесна.
Может быть, вы достигли какого-то предела, например установить с помощью setrlimit (2) где-нибудь еще.
Пытаться cat /proc/self/limits
найти их (в Linux).
использование Трассирование (1) (например, как strace -f yourprogram
) выяснить, что происходит; осмотреться fork
или же clone
линия…
КСТАТИ, Система (3) должен вернуть код ошибки при сбое. Вы должны проверить это. И вы можете позвонить system("echo here pid $$");
вместо system(NULL);