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);

Другие вопросы по тегам