diff options
| author | Corey Richardson <corey@octayn.net> | 2013-11-13 05:21:38 -0500 |
|---|---|---|
| committer | Alex Crichton <alex@alexcrichton.com> | 2014-01-24 22:30:00 -0800 |
| commit | dee7fa58dd4203a19b83ad47c3b0a0efb92c0e9a (patch) | |
| tree | adb1ec1b8219633b3a7d65611cd2a1648d658f61 /src/libgreen | |
| parent | 462f09e9494481456b22630cb42a3c0544a08625 (diff) | |
| download | rust-dee7fa58dd4203a19b83ad47c3b0a0efb92c0e9a.tar.gz rust-dee7fa58dd4203a19b83ad47c3b0a0efb92c0e9a.zip | |
Use `mmap` to map in task stacks and guard page
Also implement caching of stacks.
Diffstat (limited to 'src/libgreen')
| -rw-r--r-- | src/libgreen/context.rs | 6 | ||||
| -rw-r--r-- | src/libgreen/coroutine.rs | 10 | ||||
| -rw-r--r-- | src/libgreen/stack.rs | 121 |
3 files changed, 103 insertions, 34 deletions
diff --git a/src/libgreen/context.rs b/src/libgreen/context.rs index 8530e3e837e..3f2f74bbb8d 100644 --- a/src/libgreen/context.rs +++ b/src/libgreen/context.rs @@ -12,10 +12,9 @@ use std::libc::c_void; use std::uint; use std::cast::{transmute, transmute_mut_unsafe, transmute_region, transmute_mut_region}; +use stack::Stack; use std::unstable::stack; -use stack::StackSegment; - // FIXME #7761: Registers is boxed so that it is 16-byte aligned, for storing // SSE regs. It would be marginally better not to do this. In C++ we // use an attribute on a struct. @@ -41,7 +40,7 @@ impl Context { } /// Create a new context that will resume execution by running proc() - pub fn new(start: proc(), stack: &mut StackSegment) -> Context { + pub fn new(start: proc(), stack: &mut Stack) -> Context { // The C-ABI function that is the task entry point // // Note that this function is a little sketchy. We're taking a @@ -79,6 +78,7 @@ impl Context { // be passed to the spawn function. Another unfortunate // allocation let start = ~start; + initialize_call_frame(&mut *regs, task_start_wrapper as *c_void, unsafe { transmute(&*start) }, diff --git a/src/libgreen/coroutine.rs b/src/libgreen/coroutine.rs index 7bc5d0accfe..3d7dc58a1b2 100644 --- a/src/libgreen/coroutine.rs +++ b/src/libgreen/coroutine.rs @@ -14,7 +14,7 @@ use std::rt::env; use context::Context; -use stack::{StackPool, StackSegment}; +use stack::{StackPool, Stack}; /// A coroutine is nothing more than a (register context, stack) pair. pub struct Coroutine { @@ -24,7 +24,7 @@ pub struct Coroutine { /// /// Servo needs this to be public in order to tell SpiderMonkey /// about the stack bounds. - current_stack_segment: StackSegment, + current_stack_segment: Stack, /// Always valid if the task is alive and not running. saved_context: Context @@ -39,7 +39,7 @@ impl Coroutine { Some(size) => size, None => env::min_stack() }; - let mut stack = stack_pool.take_segment(stack_size); + let mut stack = stack_pool.take_stack(stack_size); let initial_context = Context::new(start, &mut stack); Coroutine { current_stack_segment: stack, @@ -49,7 +49,7 @@ impl Coroutine { pub fn empty() -> Coroutine { Coroutine { - current_stack_segment: StackSegment::new(0), + current_stack_segment: Stack::new(0), saved_context: Context::empty() } } @@ -57,6 +57,6 @@ impl Coroutine { /// Destroy coroutine and try to reuse std::stack segment. pub fn recycle(self, stack_pool: &mut StackPool) { let Coroutine { current_stack_segment, .. } = self; - stack_pool.give_segment(current_stack_segment); + stack_pool.give_stack(current_stack_segment); } } diff --git a/src/libgreen/stack.rs b/src/libgreen/stack.rs index 7e6dd02dd67..a5d5174b91b 100644 --- a/src/libgreen/stack.rs +++ b/src/libgreen/stack.rs @@ -8,46 +8,101 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::vec; -use std::libc::{c_uint, uintptr_t}; +use std::rt::env::max_cached_stacks; +use std::os::{errno, page_size, MemoryMap, MapReadable, MapWritable, MapNonStandardFlags}; +#[cfg(not(windows))] +use std::libc::{MAP_STACK, MAP_PRIVATE, MAP_ANON}; +use std::libc::{c_uint, c_int, c_void, uintptr_t}; -pub struct StackSegment { - priv buf: ~[u8], - priv valgrind_id: c_uint +/// A task's stack. The name "Stack" is a vestige of segmented stacks. +pub struct Stack { + priv buf: MemoryMap, + priv min_size: uint, + priv valgrind_id: c_uint, } -impl StackSegment { - pub fn new(size: uint) -> StackSegment { - unsafe { - // Crate a block of uninitialized values - let mut stack = vec::with_capacity(size); - stack.set_len(size); +// Try to use MAP_STACK on platforms that support it (it's what we're doing +// anyway), but some platforms don't support it at all. For example, it appears +// that there's a bug in freebsd that MAP_STACK implies MAP_FIXED (so it always +// fails): http://lists.freebsd.org/pipermail/freebsd-bugs/2011-July/044840.html +#[cfg(not(windows), not(target_os = "freebsd"))] +static STACK_FLAGS: c_int = MAP_STACK | MAP_PRIVATE | MAP_ANON; +#[cfg(target_os = "freebsd")] +static STACK_FLAGS: c_int = MAP_PRIVATE | MAP_ANON; +#[cfg(windows)] +static STACK_FLAGS: c_int = 0; - let mut stk = StackSegment { - buf: stack, - valgrind_id: 0 - }; +impl Stack { + pub fn new(size: uint) -> Stack { + // Map in a stack. Eventually we might be able to handle stack allocation failure, which + // would fail to spawn the task. But there's not many sensible things to do on OOM. + // Failure seems fine (and is what the old stack allocation did). + let stack = match MemoryMap::new(size, [MapReadable, MapWritable, + MapNonStandardFlags(STACK_FLAGS)]) { + Ok(map) => map, + Err(e) => fail!("Creating memory map for stack of size {} failed: {}", size, e) + }; - // XXX: Using the FFI to call a C macro. Slow - stk.valgrind_id = rust_valgrind_stack_register(stk.start(), stk.end()); - return stk; + // Change the last page to be inaccessible. This is to provide safety; when an FFI + // function overflows it will (hopefully) hit this guard page. It isn't guaranteed, but + // that's why FFI is unsafe. buf.data is guaranteed to be aligned properly. + if !protect_last_page(&stack) { + fail!("Could not memory-protect guard page. stack={:?}, errno={}", + stack, errno()); } + + let mut stk = Stack { + buf: stack, + min_size: size, + valgrind_id: 0 + }; + + // XXX: Using the FFI to call a C macro. Slow + stk.valgrind_id = unsafe { rust_valgrind_stack_register(stk.start(), stk.end()) }; + return stk; } /// Point to the low end of the allocated stack pub fn start(&self) -> *uint { - self.buf.as_ptr() as *uint + self.buf.data as *uint } /// Point one word beyond the high end of the allocated stack pub fn end(&self) -> *uint { unsafe { - self.buf.as_ptr().offset(self.buf.len() as int) as *uint + self.buf.data.offset(self.buf.len as int) as *uint } } } -impl Drop for StackSegment { +// These use ToPrimitive so that we never need to worry about the sizes of whatever types these +// (which we would with scalar casts). It's either a wrapper for a scalar cast or failure: fast, or +// will fail during compilation. +#[cfg(unix)] +fn protect_last_page(stack: &MemoryMap) -> bool { + use std::libc::{mprotect, PROT_NONE, size_t}; + unsafe { + // This may seem backwards: the start of the segment is the last page? Yes! The stack grows + // from higher addresses (the end of the allocated block) to lower addresses (the start of + // the allocated block). + let last_page = stack.data as *c_void; + mprotect(last_page, page_size() as size_t, PROT_NONE) != -1 + } +} + +#[cfg(windows)] +fn protect_last_page(stack: &MemoryMap) -> bool { + use std::libc::{VirtualProtect, PAGE_NOACCESS, SIZE_T, LPDWORD, DWORD}; + unsafe { + // see above + let last_page = stack.data as *mut c_void; + let mut old_prot: DWORD = 0; + VirtualProtect(last_page, page_size() as SIZE_T, PAGE_NOACCESS, + &mut old_prot as LPDWORD) != 0 + } +} + +impl Drop for Stack { fn drop(&mut self) { unsafe { // XXX: Using the FFI to call a C macro. Slow @@ -56,16 +111,30 @@ impl Drop for StackSegment { } } -pub struct StackPool(()); +pub struct StackPool { + // Ideally this would be some datastructure that preserved ordering on Stack.min_size. + priv stacks: ~[Stack], +} impl StackPool { - pub fn new() -> StackPool { StackPool(()) } + pub fn new() -> StackPool { + StackPool { + stacks: ~[], + } + } - pub fn take_segment(&self, min_size: uint) -> StackSegment { - StackSegment::new(min_size) + pub fn take_stack(&mut self, min_size: uint) -> Stack { + // Ideally this would be a binary search + match self.stacks.iter().position(|s| s.min_size < min_size) { + Some(idx) => self.stacks.swap_remove(idx), + None => Stack::new(min_size) + } } - pub fn give_segment(&self, _stack: StackSegment) { + pub fn give_stack(&mut self, stack: Stack) { + if self.stacks.len() <= max_cached_stacks() { + self.stacks.push(stack) + } } } |
