@@ -7,18 +7,100 @@ use crate::prelude::*;
77
88use self :: ioctl:: { Categories , PageMapScanBuilder } ;
99use crate :: runtime:: vm:: { HostAlignedByteCount , host_page_size} ;
10+ use rustix:: fd:: AsRawFd ;
1011use rustix:: ioctl:: ioctl;
1112use std:: fs:: File ;
1213use std:: mem:: MaybeUninit ;
1314use std:: ptr;
15+ use std:: sync:: LazyLock ;
16+
17+ /// A static file-per-process which represents this process's page map file.
18+ ///
19+ /// Note that this is required to be updated on a fork because otherwise this'll
20+ /// refer to the parent process's page map instead of the child process's page
21+ /// map. Thus when first initializing this file the `pthread_atfork` function is
22+ /// used to hook the child process to update this.
23+ ///
24+ /// Also note that updating this is not done via mutation but rather it's done
25+ /// with `dup2` to replace the file descriptor that `File` points to in-place.
26+ /// The local copy of of `File` is then closed in the atfork handler.
27+ static PROCESS_PAGEMAP : LazyLock < Option < File > > = LazyLock :: new ( || {
28+ let pagemap = File :: open ( "/proc/self/pagemap" ) . ok ( ) ?;
29+
30+ // SAFETY: all libc functions are unsafe by default, and we're basically
31+ // going to do our damndest to make sure this invocation of `pthread_atfork`
32+ // is safe, namely the handler registered here is intentionally quite
33+ // minimal and only accesses the `PROCESS_PAGEMAP`.
34+ let rc = unsafe { libc:: pthread_atfork ( None , None , Some ( after_fork_in_child) ) } ;
35+ if rc != 0 {
36+ return None ;
37+ }
38+
39+ return Some ( pagemap) ;
40+
41+ /// Hook executed as part of `pthread_atfork` in the child process after a
42+ /// fork.
43+ ///
44+ /// # Safety
45+ ///
46+ /// This function is not safe to call in general and additionally has its
47+ /// own stringent safety requirements. This is after a fork but before exec
48+ /// so all the safety requirements of `Command::pre_exec` in the standard
49+ /// library apply here. Effectively the standard library primitives are
50+ /// avoided here as they aren't necessarily safe to execute in this context.
51+ unsafe extern "C" fn after_fork_in_child ( ) {
52+ let Some ( parent_pagemap) = PROCESS_PAGEMAP . as_ref ( ) else {
53+ // This should not be reachable, but to avoid panic infrastructure
54+ // here this is just skipped instead.
55+ return ;
56+ } ;
57+
58+ // SAFETY: see function documentation.
59+ //
60+ // Here `/proc/self/pagemap` is opened in the child. If that fails for
61+ // whatever reason then the pagemap is replaced with `/dev/null` which
62+ // means that all future ioctls for `PAGEMAP_SCAN` will fail. If that
63+ // fails then that's left to abort the process for now. If that's
64+ // problematic we may want to consider opening a local pipe and then
65+ // installing that here? Unsure.
66+ //
67+ // Once a fd is opened the `dup2` syscall is used to replace the
68+ // previous file descriptor stored in `parent_pagemap`. That'll update
69+ // the pagemap in-place in this child for all future use in case this is
70+ // further used in the child.
71+ //
72+ // And finally once that's all done the `child_pagemap` is itself
73+ // closed since we have no more need for it.
74+ unsafe {
75+ let flags = libc:: O_CLOEXEC | libc:: O_RDONLY ;
76+ let mut child_pagemap = libc:: open ( c"/proc/self/pagemap" . as_ptr ( ) , flags) ;
77+ if child_pagemap == -1 {
78+ child_pagemap = libc:: open ( c"/dev/null" . as_ptr ( ) , flags) ;
79+ }
80+ if child_pagemap == -1 {
81+ libc:: abort ( ) ;
82+ }
83+
84+ let rc = libc:: dup2 ( child_pagemap, parent_pagemap. as_raw_fd ( ) ) ;
85+ if rc == -1 {
86+ libc:: abort ( ) ;
87+ }
88+ let rc = libc:: close ( child_pagemap) ;
89+ if rc == -1 {
90+ libc:: abort ( ) ;
91+ }
92+ }
93+ }
94+ } ) ;
1495
1596#[ derive( Debug ) ]
16- pub struct PageMap ( File ) ;
97+ pub struct PageMap ( & ' static File ) ;
1798
1899impl PageMap {
19100 #[ cfg( feature = "pooling-allocator" ) ]
20101 pub fn new ( ) -> Option < PageMap > {
21- let file = File :: open ( "/proc/self/pagemap" ) . ok ( ) ?;
102+ let file = PROCESS_PAGEMAP . as_ref ( ) ?;
103+
22104 // Check if the `pagemap_scan` ioctl is supported.
23105 let mut regions = vec ! [ MaybeUninit :: uninit( ) ; 1 ] ;
24106 let pm_scan = PageMapScanBuilder :: new ( ptr:: slice_from_raw_parts ( ptr:: null_mut ( ) , 0 ) )
0 commit comments