3 ms·
That doesn't work for the stack. Consider a recursive function. The amount of memory it could need is essentially infinite, though obviously stuff breaks when
by burfog 9y ago
That doesn't work for the stack.
Consider a recursive function. The amount of memory it could need is essentially infinite, though obviously stuff breaks when the address space wraps around or when the stack collides with a different allocation.
So, do we just prohibit recursion? Do we need to have a privileged compiler (making signed executables) that refuses to generate code unless the stack usage can be proven at compile time?
Pretty much, if recursion is supported, you have overcommit on some level. For example, if we force developers to specify a stack size, we still have a form of overcommit. We've just moved it to the developer making promises that he probably can't verify to be safe.
- dooglius 9y ago> So, do we just prohibit recursion? This is what MISRA C does > Do we need to have a privileged compiler (making signed executables) that refuses to generate code unless the stack usage can be proven at compile time? This is roughly what the Linux kernel does (I believe it's more a set of checks that aren't formally proven) So yes, if you want to have run a process reliably, you shouldn't be doing any potentially unbounded recursion. > We've just moved it to the developer making promises that he probably can't verify to be safe. I don't agree, programs get a pretty large amount of stack space by default, and as long as you aren't doing any crazy amount of alloca or recursion (which is in fact the case for most programs), you'll be fine. They may not be formally verified to be safe, but practically the are. OTOH, the OOM killer can take you down regardless of what you're doing, based on what other processes are doing, so developers can have no assurance whatsoever that their programs won't suddenly die. Anecdotally, I've only even seen stack overflows due to infinite recursion bugs that are caught during development, but I see OOM-related problems not at all infrequently.
- geofft 9y agoI don't think I'd call that "overcommit." Overcommit is when there's more virtual memory (allegedly) available than actual backing physical memory (RAM+swap). You don't need overcommit to implement a fixed-size stack: just put a guard page (a page table entry that is intentionally invalid and reserved) at the end of the stack. If the stack grows into the guard page, you get a segfault. The actual stack pages for each thread can still be physically reserved. It still does result in killed processes by default - but you can set up a segfault handler with an alternate (preallocated and non-overcommitted) stack which can handle it gracefully, unlike the OOM killer's SIGKILL. Or you can write your code so that each function call checks that stack space is available before using it. Go and Rust both used to do this, via a function called __morestack, because they both used to support segmented stacks. GCC and LLVM both support this scheme. (And for a while Rust still called __morestack for the purpose of printing a nicer error message without having to set a segfault handler: https://ldpreload.com/blog/stack-smashes-you https://ldpreload.com/blog/stack-smashes-you ) But "overcommit" isn't a synonym for "something that kills processes when they exceed their memory allocation."
- burfog 9y ago"Overcommit" is when the processes running on the system have been promised more memory than is available. If you run an ordinary Linux program, exactly how much memory should be allocated to the stack? That question is essentially unanswered. At the ABI level, considering just the ELF binary itself, there is no limit in place. (no fixed size) This is a promise to provide unlimited memory. It is obvious that giving unlimited memory to every running process is not possible. If we were to eliminate overcommit and attempt to boot the system, the init process could not legitimately start. That process implicitly requests an unlimited amount of memory for the stack, which is obviously unavailable, so it can not be started.
- geofft 9y ago> If you run an ordinary Linux program, exactly how much memory should be allocated to the stack? That question is essentially unanswered. Not at all: (initramfs) grep stack /proc/1/limits Max stack size 8388608 unlimited bytes (initramfs) grep stack /proc/1/maps 7ffca6fb3000-7ffca6fd4000 rw-p 00000000 00:00 0 [stack] init can request more than 8 MB if it needs it, but the binary starts with 128 kB allocated (and more faultable if you hit the guard page) and an 8 MB limit. It can change its own rlimits, sure, but it needs to do that within its existing 8 MB stack. It would be perfectly straightforward to permit setrlimit to fail if you request too much of a stack, and disable overcommit entirely. So init can raise its stack by some amount, but not more than the available physical memory on the machine. See also public header file <linux/resource.h>: /* * Limit the stack by to some sane default: root can always * increase this limit if needed.. 8MB seems reasonable. */ #define _STK_LIM (8*1024*1024) > At the ABI level, considering just the ELF binary itself, there is no limit in place. (no fixed size) This is a promise to provide unlimited memory. The ABI also provides no limit on the size of bss, or on the size of the program itself. This is hardly a promise to provide unlimited memory for global variables or program text or anything else. The amount of memory is finite and unspecified by the SysV ABI and left up to implementations.
- burfog 9y ago