我正在创建一个程序,它使用
userfaultfd
来操作子进程的某些页面。我需要这个程序来监控所有的子程序,所以我正在用EVENT_FORK
收听叉子。但是,除非我先register
一个虚拟页面,否则这不起作用:
Cargo.toml:
[package]
name = "..."
version = "0.1.0"
edition = "2021"
[dependencies]
nix = "0.26.2"
rustix = { version = "0.37.3", features = ["mm"] }
userfaultfd = "0.5.1"
main.rs:
use {nix::unistd, rustix::mm, std::ptr};
fn main() {
// Allocate a "dummy" page for uffd to register
println!("Allocating dummy page");
let dummy_page_size = 4096;
let dummy_page = unsafe {
mm::mmap_anonymous(
ptr::null_mut(),
dummy_page_size,
mm::ProtFlags::READ | mm::ProtFlags::WRITE,
mm::MapFlags::PRIVATE,
)
.expect("Unable to allocate memory")
};
// Initialize uffd
println!("Building uffd");
let uffd = userfaultfd::UffdBuilder::new()
.require_features(userfaultfd::FeatureFlags::EVENT_FORK)
.require_ioctls(userfaultfd::IoctlFlags::empty())
.close_on_exec(false)
.user_mode_only(true)
.non_blocking(false)
.create()
.expect("Unable to create uffd");
// Perform a dummy register to fully initialize the uffd.
println!("Registering dummy page");
uffd.register(dummy_page, dummy_page_size)
.expect("Unable to perform dummy register");
// Fork on a separate thread
let _ = std::thread::spawn(move || {
println!("Forking");
match unsafe { unistd::fork().expect("Unable to fork") } {
// On the parent, wait for the child
unistd::ForkResult::Parent { child } => {
println!("Waiting for child {child}");
let _ = nix::sys::wait::wait().expect("Unable to wait for child");
println!("Child {child} exited");
},
// On the child, execute the command
unistd::ForkResult::Child => {
println!("Executing child");
std::thread::sleep(std::time::Duration::from_secs(1));
},
}
});
println!("Entering uffd event loop");
while let Some(event) = uffd.read_event().expect("Unable to read event") {
println!("Event: {event:?}");
}
println!("Exiting uffd event loop");
}
输出:
Allocating dummy page
Building uffd
Registering dummy page
Entering uffd event loop
Forking
Event: Fork { uffd: Uffd { fd: 4 } }
Waiting for child 228151
Executing child
Child 228151 exited
重要的是收到的
Event: Fork { uffd: Uffd { fd: 4 } }
事件。
去掉
uffd.register(dummy_page, dummy_page_size)
部分,输出如下(不管mmap
分配是否发生)
Allocating dummy page
Building uffd
Registering dummy page
Entering uffd event loop
Forking
Waiting for child 228349
Executing child
Child 228349 exited
其中不包含 fork 事件。这是为什么?
编辑:
即使使用虚拟寄存器,如果子进程本身进行分叉,也不会收到分叉事件。 我还没有测试过在孩子身上写寄存器是否有效,但我怀疑这会让我看到孩子的叉子。
运行时,如果相关的话,我使用
sudo setcap cap_sys_ptrace=ep ./target/debug/...
来运行程序。
执行像
uffd.register(std::ptr::null_mut(), 0).unwrap_err()
这样的无效注册不会导致分叉事件。
在做虚拟寄存器时,
fork
调用停止,直到read_event
在主线程中发生。
当不做虚拟寄存器时,fork
根本不等待。