时间:2019-03-08 标签:c++system()raisesENOMEM
c++ system() raises ENOMEM
这个问题是M(not)WE of this question。我写了一个重现错误的代码:
#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<<std::system(NULL)<<std::endl;
}
它必须用 g++ -ggdb -std=c++11
(Debian 上的 g++ 4.9)编译。笔记
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
在第 9 行设置为 ENOMEM
,对应于
system()
打电话。请注意,如果向量的尺寸较小(我猜它
取决于您将 运行 代码的计算机),代码工作正常并且
system(NULL)
returns 1 当 shell 可用时应该如此。
为什么旗帜 ENOMEM
升起?为什么代码不使用交换内存?这是一个错误吗?有解决方法吗? popen
或 exec*
会做同样的事情吗? (我知道,每个 post 我应该只问一个问题,但所有这些问题都可以总结为“发生了什么事?”)
根据要求,这是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
这里是免费的输出:
total used free shared buffers cached
Mem: 7915060 1668928 6246132 49576 34668 1135612
-/+ buffers/cache: 498648 7416412
Swap: 2928636 0 2928636
你的线路
std::vector<double> a(7e8,1);
可能是错误的。您正在为 std::vector 调用一个构造函数,它接受一个向量大小和一个初始化元素。 7e8 被转换为一个巨大的尺寸(即 700000000 个元素)。
您可能想要构造一个二元向量,因此请使用
std::vector<double> a{7e8,1};
使用你的巨大向量,system(3) library function will call fork(2) 系统调用失败:
ENOMEM fork()
failed to allocate the necessary kernel structures
because memory is tight.
也许您达到了某个限制,例如由 setrlimit(2) 在其他地方设置。
尝试 cat /proc/self/limits
找到它们(在 Linux)。
使用strace(1)(例如strace -f yourprogram
)找出正在发生的事情;环顾 fork
或 clone
行...
顺便说一句,system(3) 应该 return 失败时的错误代码。你应该测试一下。你可能想调用 system("echo here pid $$");
而不是 system(NULL);
system()
函数首先使用 fork()
或类似的方法创建进程的新副本(在 Linux 中,这最终在 clone()
系统调用中,如您所示),然后在子进程中调用 exec
来创建 shell 运行 所需的命令。
如果新进程的虚拟内存不足,fork()
调用可能会失败(即使您打算立即用更小的占用空间替换它,内核也不知道)。某些系统允许您通过写时复制(vfork()
)或内存过量使用(/proc/sys/vm/overcommit_memory
和 /proc/sys/vm/overcommit_ratio
)来交换分叉大型进程的能力,以减少页面错误可能失败的保证.
请注意,以上内容同样适用于任何可能创建新进程的库函数 - 例如popen()
。虽然不是 exec()
,因为 替换了 进程并且不克隆它。
如果提供的机制不适合您的用例,那么您可能需要实施自己的 system()
替换。我建议尽早启动子进程(在分配大量内存之前),其唯一工作是在 stdin
上接受 NUL
分隔的命令行并在 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 进行读写操作等。
这个问题是M(not)WE of this question。我写了一个重现错误的代码:
#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<<std::system(NULL)<<std::endl;
}
它必须用 g++ -ggdb -std=c++11
(Debian 上的 g++ 4.9)编译。笔记
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
在第 9 行设置为 ENOMEM
,对应于
system()
打电话。请注意,如果向量的尺寸较小(我猜它
取决于您将 运行 代码的计算机),代码工作正常并且
system(NULL)
returns 1 当 shell 可用时应该如此。
为什么旗帜 ENOMEM
升起?为什么代码不使用交换内存?这是一个错误吗?有解决方法吗? popen
或 exec*
会做同样的事情吗? (我知道,每个 post 我应该只问一个问题,但所有这些问题都可以总结为“发生了什么事?”)
根据要求,这是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
这里是免费的输出:
total used free shared buffers cached
Mem: 7915060 1668928 6246132 49576 34668 1135612
-/+ buffers/cache: 498648 7416412
Swap: 2928636 0 2928636
你的线路
std::vector<double> a(7e8,1);
可能是错误的。您正在为 std::vector 调用一个构造函数,它接受一个向量大小和一个初始化元素。 7e8 被转换为一个巨大的尺寸(即 700000000 个元素)。
您可能想要构造一个二元向量,因此请使用
std::vector<double> a{7e8,1};
使用你的巨大向量,system(3) library function will call fork(2) 系统调用失败:
ENOMEM
fork()
failed to allocate the necessary kernel structures because memory is tight.
也许您达到了某个限制,例如由 setrlimit(2) 在其他地方设置。
尝试 cat /proc/self/limits
找到它们(在 Linux)。
使用strace(1)(例如strace -f yourprogram
)找出正在发生的事情;环顾 fork
或 clone
行...
顺便说一句,system(3) 应该 return 失败时的错误代码。你应该测试一下。你可能想调用 system("echo here pid $$");
而不是 system(NULL);
system()
函数首先使用 fork()
或类似的方法创建进程的新副本(在 Linux 中,这最终在 clone()
系统调用中,如您所示),然后在子进程中调用 exec
来创建 shell 运行 所需的命令。
如果新进程的虚拟内存不足,fork()
调用可能会失败(即使您打算立即用更小的占用空间替换它,内核也不知道)。某些系统允许您通过写时复制(vfork()
)或内存过量使用(/proc/sys/vm/overcommit_memory
和 /proc/sys/vm/overcommit_ratio
)来交换分叉大型进程的能力,以减少页面错误可能失败的保证.
请注意,以上内容同样适用于任何可能创建新进程的库函数 - 例如popen()
。虽然不是 exec()
,因为 替换了 进程并且不克隆它。
如果提供的机制不适合您的用例,那么您可能需要实施自己的 system()
替换。我建议尽早启动子进程(在分配大量内存之前),其唯一工作是在 stdin
上接受 NUL
分隔的命令行并在 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 进行读写操作等。