userfaultfd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +142 -0
- data/docs/gvl-and-page-faults.md +58 -0
- data/examples/dirty_tracking.rb +30 -0
- data/examples/lazy_mmap.rb +19 -0
- data/examples/oom_canary.rb +26 -0
- data/examples/post_copy_migration.rb +29 -0
- data/ext/userfaultfd/compat.h +237 -0
- data/ext/userfaultfd/constants.c +102 -0
- data/ext/userfaultfd/extconf.rb +12 -0
- data/ext/userfaultfd/userfaultfd.c +2010 -0
- data/lib/userfaultfd/handler.rb +76 -0
- data/lib/userfaultfd/version.rb +5 -0
- data/lib/userfaultfd.rb +58 -0
- data/sig/userfaultfd.rbs +93 -0
- metadata +59 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c4c0a7c4dc065c872850b1c6d1cec3c19130f606c7ecad76efc0403e84418693
|
|
4
|
+
data.tar.gz: 718b058ac60f5abff6c5f66d22b9e7a38c82c4b704490f669762c16994bdbba1
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 138ef38522fc668d6e92ec41d38f05451eece15f5518949c2197bf6020ebf3839cd193ad0f22fc8a9bfe84dc2c2f3d94766b18beb8f630da220b25974b57aca9
|
|
7
|
+
data.tar.gz: 0ce48388e31b212bcd9553cdd3243fd6d20c6a9071f311613e212b2767c85c6c5e2c1d6b0d01130728097c9a017449a1da81858ec197e6ee51a3da396f7fae1a
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yudai Takada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# userfaultfd
|
|
2
|
+
|
|
3
|
+
Ruby bindings for Linux `userfaultfd(2)`, with mmap-backed regions and handlers that avoid blocking Ruby's Global VM Lock (GVL).
|
|
4
|
+
|
|
5
|
+
## The deadlock rule
|
|
6
|
+
|
|
7
|
+
A Ruby thread must never touch registered memory through a raw pointer while a Ruby callback is expected to resolve the fault. The faulting thread keeps the GVL, so the callback cannot run.
|
|
8
|
+
|
|
9
|
+
Use one of these supported arrangements:
|
|
10
|
+
|
|
11
|
+
- `Region#read` / `Region#write`: the C extension releases the GVL around memory access.
|
|
12
|
+
- A forked faulting process: the parent callback has a separate GVL. `UFFD_FEATURE_EVENT_FORK` also requires `CAP_SYS_PTRACE`; check `enabled_features`.
|
|
13
|
+
- A native `:zero_fill`, `:backing_file`, or `:prefilled` handler: the handler never enters Ruby.
|
|
14
|
+
|
|
15
|
+
See [docs/gvl-and-page-faults.md](docs/gvl-and-page-faults.md) for the full failure sequence and implementation details.
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- Linux 4.11 or newer with `CONFIG_USERFAULTFD`
|
|
20
|
+
- Ruby 3.2 or newer
|
|
21
|
+
- A C compiler; the Linux 4.11 baseline UAPI is bundled for build hosts without `linux/userfaultfd.h`
|
|
22
|
+
- Permission through `/dev/userfaultfd`, or the `userfaultfd(2)` syscall with `UFFD_USER_MODE_ONLY`
|
|
23
|
+
|
|
24
|
+
The gem tries `/dev/userfaultfd` first and falls back to the syscall. Requiring the gem succeeds on unsupported systems; `UserfaultFD.supported?` returns `false`, and opening a descriptor raises `UserfaultFD::UnsupportedError` or the relevant `Errno::*` permission error. Linux before 5.11 does not understand `UFFD_USER_MODE_ONLY`; an appropriately permitted caller can explicitly use `user_mode_only: false` with `.supported?`, `.features`, and `.new`. This opts into kernel-originated faults and is never automatic.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
gem "userfaultfd"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then run `bundle install`. The `userfaultfd` RubyGems name was unclaimed when this project was initialized; until it is published, use the repository source in your Gemfile.
|
|
33
|
+
|
|
34
|
+
## Native zero fill
|
|
35
|
+
|
|
36
|
+
This mode is safe even when the main Ruby thread faults:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
require "userfaultfd"
|
|
40
|
+
|
|
41
|
+
region = UserfaultFD::Region.new(size: 64 * 1024 * 1024)
|
|
42
|
+
uffd = UserfaultFD.new(features: [])
|
|
43
|
+
uffd.register(region, mode: :missing)
|
|
44
|
+
handler = uffd.start_handler(mode: :zero_fill)
|
|
45
|
+
|
|
46
|
+
data = region.read(0, region.size)
|
|
47
|
+
raise "not zero" unless data == "\0".b * region.size
|
|
48
|
+
|
|
49
|
+
handler.stop
|
|
50
|
+
uffd.close
|
|
51
|
+
region.unmap
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Ruby callback in one process
|
|
55
|
+
|
|
56
|
+
`Region#read` releases the GVL, allowing the handler's Ruby block to run:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
page_size = UserfaultFD::Region.allocate.page_size
|
|
60
|
+
region = UserfaultFD::Region.new(size: page_size)
|
|
61
|
+
uffd = UserfaultFD.new(features: [])
|
|
62
|
+
uffd.register(region, mode: :missing)
|
|
63
|
+
handler = uffd.start_handler do |event|
|
|
64
|
+
event.copy("x" * region.page_size) if event.is_a?(UserfaultFD::Fault)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
region.read(0, region.page_size) #=> "x" * page_size
|
|
68
|
+
|
|
69
|
+
handler.stop
|
|
70
|
+
uffd.close
|
|
71
|
+
region.unmap
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Forked faulting process
|
|
75
|
+
|
|
76
|
+
When permitted, `UserfaultFD.new` enables `EVENT_FORK` by default. A native event reader consumes the fork event without waiting for the GVL, then delivers `ForkEvent` and child faults to the Ruby handler.
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
page_size = UserfaultFD::Region.allocate.page_size
|
|
80
|
+
region = UserfaultFD::Region.new(size: page_size)
|
|
81
|
+
uffd = UserfaultFD.new
|
|
82
|
+
raise "EVENT_FORK needs CAP_SYS_PTRACE" unless uffd.enabled_features.include?(:event_fork)
|
|
83
|
+
|
|
84
|
+
uffd.register(region, mode: :missing)
|
|
85
|
+
handler = uffd.start_handler do |event|
|
|
86
|
+
case event
|
|
87
|
+
when UserfaultFD::Fault
|
|
88
|
+
event.copy("child".ljust(region.page_size, "\0"))
|
|
89
|
+
when UserfaultFD::ForkEvent
|
|
90
|
+
# The handler monitors and closes event.child_uffd automatically.
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
pid = fork { exit!(region.read(0, 5) == "child" ? 0 : 1) }
|
|
95
|
+
Process.wait(pid)
|
|
96
|
+
|
|
97
|
+
handler.stop
|
|
98
|
+
uffd.close
|
|
99
|
+
region.unmap
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Other operations
|
|
103
|
+
|
|
104
|
+
- `Fault#zero`, `#copy`, `#wake`, `#continue`, `#poison`, and `#move`
|
|
105
|
+
- `UserfaultFD#writeprotect` with `mode: [:missing, :wp]`
|
|
106
|
+
- `Region#madvise(:dontneed)` to drop page-table entries and `:remove` to discard shared pages
|
|
107
|
+
- `mode: :backing_file, io: file` for native lazy file loading
|
|
108
|
+
- `mode: :prefilled, source: region` for native copies from another region
|
|
109
|
+
- `ForkEvent`, `RemapEvent`, `RemoveEvent`, and `UnmapEvent`
|
|
110
|
+
- `UserfaultFD.features` for kernel capabilities and `#enabled_features` for this descriptor
|
|
111
|
+
|
|
112
|
+
Optional ioctls raise `UnsupportedError` when they are absent from the build headers or running kernel.
|
|
113
|
+
|
|
114
|
+
## Unsupported and constrained environments
|
|
115
|
+
|
|
116
|
+
| Environment | Result |
|
|
117
|
+
|---|---|
|
|
118
|
+
| macOS, BSD, Windows | `require` works; `supported?` is `false` |
|
|
119
|
+
| Linux before 4.11 or without `CONFIG_USERFAULTFD` | Unsupported |
|
|
120
|
+
| Linux 4.11–5.10 | An appropriately permitted caller must explicitly pass `user_mode_only: false` |
|
|
121
|
+
| Docker with the default seccomp profile | The syscall is commonly blocked; allow `userfaultfd` or use an accessible `/dev/userfaultfd` |
|
|
122
|
+
| Docker Desktop / WSL2 | Depends on the VM kernel, config, device, and seccomp policy rather than the guest userspace alone |
|
|
123
|
+
| Linux with `vm.unprivileged_userfaultfd=0` | User-mode-only faults still work on kernels that honor `UFFD_USER_MODE_ONLY`; fork events require `CAP_SYS_PTRACE` |
|
|
124
|
+
| GitHub-hosted runners | Unit tests run; system tests skip when the host policy denies userfaultfd |
|
|
125
|
+
| Kernels before 6.6 / 6.8 | `POISON` / `MOVE` capabilities are absent |
|
|
126
|
+
|
|
127
|
+
Never register Ruby's object heap. Registered memory must come from `UserfaultFD::Region`; otherwise GC or ordinary Ruby code can fault while holding the GVL.
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
rake compile
|
|
133
|
+
rake test:unit
|
|
134
|
+
rake test:system
|
|
135
|
+
rbs -I sig validate
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
System examples use timeouts because an unresolved page fault intentionally blocks its faulting process. The kernel matrix runner is in `tools/vm`.
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT. See [LICENSE.txt](LICENSE.txt).
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# GVL and userfaultfd page faults
|
|
2
|
+
|
|
3
|
+
## Why the naive Ruby callback deadlocks
|
|
4
|
+
|
|
5
|
+
The kernel suspends a thread that faults on a registered page until another thread resolves the event. If ordinary Ruby code performs that access through a raw pointer, the suspended thread still owns the GVL:
|
|
6
|
+
|
|
7
|
+
1. Ruby thread A touches the registered address while holding the GVL.
|
|
8
|
+
2. The kernel suspends A and publishes `UFFD_EVENT_PAGEFAULT`.
|
|
9
|
+
3. Handler thread B reads the event.
|
|
10
|
+
4. B needs the GVL before it can invoke the Ruby block.
|
|
11
|
+
5. A cannot release the GVL until the fault is resolved, while B cannot resolve it until A releases the GVL.
|
|
12
|
+
|
|
13
|
+
`spec/system/userfaultfd_spec.rb` fixes this behavior as an intentional test: a child process performs a `Fiddle::Pointer` read, the test confirms it remains blocked for two seconds, then sends `SIGKILL`. The timeout and process isolation prevent a failed test from hanging the suite.
|
|
14
|
+
|
|
15
|
+
## Arrangement B: GVL-free memory access
|
|
16
|
+
|
|
17
|
+
`Region#read` and `Region#write` copy memory inside `rb_thread_call_without_gvl`. A native event-reader thread moves kernel messages into a pipe. The Ruby handler can therefore acquire the GVL, create `Fault` objects, and call `UFFDIO_COPY` or `UFFDIO_ZEROPAGE`.
|
|
18
|
+
|
|
19
|
+
The resolution ioctls also run without the GVL. Strings passed to `Fault#copy` are copied into C-owned memory first, so Ruby GC cannot move or free the source while the ioctl is running.
|
|
20
|
+
|
|
21
|
+
## Arrangement A: process separation and fork events
|
|
22
|
+
|
|
23
|
+
Fork handling has an extra cycle: `EVENT_FORK` must be read before `fork(2)` completes, but the Ruby thread invoking `fork` holds the GVL. A Ruby event-reader thread therefore cannot make progress.
|
|
24
|
+
|
|
25
|
+
The extension uses a C event-reader thread that never enters Ruby while polling the parent and child descriptors. It reads `EVENT_FORK`, starts monitoring the child fd, and writes fixed-size records to a pipe. Once `fork` returns and Ruby can schedule again, the handler converts those records into `ForkEvent` and `Fault` objects.
|
|
26
|
+
|
|
27
|
+
This requires `UFFD_FEATURE_EVENT_FORK`, which the kernel restricts with `CAP_SYS_PTRACE`. `UserfaultFD.new` tries to enable it and falls back to a descriptor without fork events on `EPERM`; callers must check `enabled_features` before using the fork arrangement.
|
|
28
|
+
|
|
29
|
+
## Arrangement C: native handlers
|
|
30
|
+
|
|
31
|
+
The native modes resolve missing faults entirely in a pthread:
|
|
32
|
+
|
|
33
|
+
- `zero_fill` issues `UFFDIO_ZEROPAGE`.
|
|
34
|
+
- `backing_file` uses `pread` and `UFFDIO_COPY`.
|
|
35
|
+
- `prefilled` copies from a second mmap-backed `Region`.
|
|
36
|
+
|
|
37
|
+
No Ruby callback or Ruby-managed buffer is involved. These modes are the appropriate choice for fault rates that a Ruby callback cannot sustain.
|
|
38
|
+
|
|
39
|
+
## Boundaries
|
|
40
|
+
|
|
41
|
+
- Do not register the Ruby heap.
|
|
42
|
+
- Do not dereference `Region#to_ptr` from Ruby while using a Ruby handler.
|
|
43
|
+
- A handler monitors at most 64 fork descendants. An epoll-backed dynamic set is the upgrade path if external process fan-out exceeds that ceiling.
|
|
44
|
+
- Native handlers currently service the descriptor on which they started; use the Ruby handler/event reader for fork descendants.
|
|
45
|
+
- Always stop handlers before closing descriptors or unmapping regions.
|
|
46
|
+
|
|
47
|
+
## Permission survey
|
|
48
|
+
|
|
49
|
+
`vm.unprivileged_userfaultfd` is a host-kernel setting, not a reliable property of the userspace distribution. Current [upstream kernel documentation](https://www.kernel.org/doc/html/latest/admin-guide/sysctl/vm.html#unprivileged-userfaultfd) and [Debian's packaged kernel documentation](https://sources.debian.org/src/linux/6.12.96-1/Documentation/admin-guide/sysctl/vm.rst) document a default of `0`: unprivileged callers must use `UFFD_USER_MODE_ONLY`. Ubuntu's [userfaultfd man page](https://manpages.ubuntu.com/manpages/jammy/man2/userfaultfd.2.html) likewise documents `EPERM` when the value is `0` and the caller lacks `CAP_SYS_PTRACE`.
|
|
50
|
+
|
|
51
|
+
Cloud images, administrators, and container hosts can override that value, and containers observe the host kernel rather than the image's `/etc/os-release`. The gem therefore probes the real syscall instead of guessing from a distribution name. Diagnose a target with:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
cat /proc/sys/vm/unprivileged_userfaultfd
|
|
55
|
+
grep USERFAULTFD /boot/config-"$(uname -r)" 2>/dev/null
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
On Linux 5.11 and newer, the default `user_mode_only: true` remains usable when the sysctl is `0`. Linux 4.11–5.10 requires the explicitly less restrictive `user_mode_only: false` and whatever privilege or sysctl policy that kernel enforces.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "userfaultfd"
|
|
4
|
+
|
|
5
|
+
abort "write-protect faults are unavailable" unless UserfaultFD.features.include?(:pagefault_flag_wp)
|
|
6
|
+
|
|
7
|
+
page_size = UserfaultFD::Region.allocate.page_size
|
|
8
|
+
region = UserfaultFD::Region.new(size: page_size)
|
|
9
|
+
uffd = UserfaultFD.new(features: [:pagefault_flag_wp])
|
|
10
|
+
uffd.register(region, mode: %i[missing wp])
|
|
11
|
+
dirty_pages = []
|
|
12
|
+
handler = uffd.start_handler do |event|
|
|
13
|
+
next unless event.is_a?(UserfaultFD::Fault)
|
|
14
|
+
|
|
15
|
+
if event.wp?
|
|
16
|
+
dirty_pages << event.address
|
|
17
|
+
uffd.writeprotect(region, enabled: false)
|
|
18
|
+
else
|
|
19
|
+
event.zero
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
region.read(0, 1)
|
|
24
|
+
uffd.writeprotect(region, enabled: true)
|
|
25
|
+
region.write(0, "changed")
|
|
26
|
+
puts "dirty pages: #{dirty_pages.length}"
|
|
27
|
+
|
|
28
|
+
handler.stop
|
|
29
|
+
uffd.close
|
|
30
|
+
region.unmap
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "userfaultfd"
|
|
4
|
+
|
|
5
|
+
path = ARGV.fetch(0)
|
|
6
|
+
file = File.open(path, "rb")
|
|
7
|
+
page_size = UserfaultFD::Region.allocate.page_size
|
|
8
|
+
size = [((file.size + page_size - 1) / page_size) * page_size, page_size].max
|
|
9
|
+
region = UserfaultFD::Region.new(size: size)
|
|
10
|
+
uffd = UserfaultFD.new(features: [])
|
|
11
|
+
uffd.register(region, mode: :missing)
|
|
12
|
+
handler = uffd.start_handler(mode: :backing_file, io: file)
|
|
13
|
+
|
|
14
|
+
puts region.read(0, [file.size, page_size].min)
|
|
15
|
+
|
|
16
|
+
handler.stop
|
|
17
|
+
uffd.close
|
|
18
|
+
region.unmap
|
|
19
|
+
file.close
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "userfaultfd"
|
|
4
|
+
|
|
5
|
+
required = %i[poison event_fork]
|
|
6
|
+
abort "UFFDIO_POISON and EVENT_FORK are required" unless (required - UserfaultFD.features).empty?
|
|
7
|
+
|
|
8
|
+
page_size = UserfaultFD::Region.allocate.page_size
|
|
9
|
+
region = UserfaultFD::Region.new(size: page_size)
|
|
10
|
+
uffd = UserfaultFD.new(features: required)
|
|
11
|
+
uffd.register(region, mode: :missing)
|
|
12
|
+
handler = uffd.start_handler do |event|
|
|
13
|
+
event.poison if event.is_a?(UserfaultFD::Fault)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
pid = fork do
|
|
17
|
+
Process.setrlimit(Process::RLIMIT_CORE, 0)
|
|
18
|
+
region.read(0, 1)
|
|
19
|
+
exit! 0
|
|
20
|
+
end
|
|
21
|
+
Process.wait(pid)
|
|
22
|
+
puts "child status: #{$?}"
|
|
23
|
+
|
|
24
|
+
handler.stop
|
|
25
|
+
uffd.close
|
|
26
|
+
region.unmap
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
require "userfaultfd"
|
|
5
|
+
|
|
6
|
+
page_size = UserfaultFD::Region.allocate.page_size
|
|
7
|
+
region = UserfaultFD::Region.new(size: page_size)
|
|
8
|
+
uffd = UserfaultFD.new
|
|
9
|
+
abort "EVENT_FORK requires CAP_SYS_PTRACE" unless uffd.enabled_features.include?(:event_fork)
|
|
10
|
+
|
|
11
|
+
sender, receiver = Socket.pair(:UNIX, :STREAM)
|
|
12
|
+
sender.write("migrated".ljust(page_size, "\0"))
|
|
13
|
+
uffd.register(region, mode: :missing)
|
|
14
|
+
handler = uffd.start_handler do |event|
|
|
15
|
+
event.copy(receiver.read(page_size)) if event.is_a?(UserfaultFD::Fault)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
pid = fork do
|
|
19
|
+
STDOUT.write("#{region.read(0, 8)}\n")
|
|
20
|
+
STDOUT.flush
|
|
21
|
+
exit! 0
|
|
22
|
+
end
|
|
23
|
+
Process.wait(pid)
|
|
24
|
+
|
|
25
|
+
handler.stop
|
|
26
|
+
uffd.close
|
|
27
|
+
region.unmap
|
|
28
|
+
sender.close
|
|
29
|
+
receiver.close
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#ifndef USERFAULTFD_COMPAT_H
|
|
2
|
+
#define USERFAULTFD_COMPAT_H
|
|
3
|
+
|
|
4
|
+
#ifdef __linux__
|
|
5
|
+
# include <stdint.h>
|
|
6
|
+
# include <sys/ioctl.h>
|
|
7
|
+
|
|
8
|
+
# ifdef HAVE_LINUX_USERFAULTFD_H
|
|
9
|
+
# include <linux/userfaultfd.h>
|
|
10
|
+
# else
|
|
11
|
+
/* Linux 4.11 baseline UAPI for build hosts without userfaultfd.h. */
|
|
12
|
+
# define UFFD_API UINT64_C(0xAA)
|
|
13
|
+
|
|
14
|
+
# define _UFFDIO_REGISTER 0x00
|
|
15
|
+
# define _UFFDIO_UNREGISTER 0x01
|
|
16
|
+
# define _UFFDIO_WAKE 0x02
|
|
17
|
+
# define _UFFDIO_COPY 0x03
|
|
18
|
+
# define _UFFDIO_ZEROPAGE 0x04
|
|
19
|
+
# define _UFFDIO_WRITEPROTECT 0x06
|
|
20
|
+
# define _UFFDIO_CONTINUE 0x07
|
|
21
|
+
# define _UFFDIO_API 0x3F
|
|
22
|
+
# define UFFDIO 0xAA
|
|
23
|
+
|
|
24
|
+
struct uffd_msg {
|
|
25
|
+
uint8_t event;
|
|
26
|
+
uint8_t reserved1;
|
|
27
|
+
uint16_t reserved2;
|
|
28
|
+
uint32_t reserved3;
|
|
29
|
+
union {
|
|
30
|
+
struct {
|
|
31
|
+
uint64_t flags;
|
|
32
|
+
uint64_t address;
|
|
33
|
+
union { uint32_t ptid; } feat;
|
|
34
|
+
} pagefault;
|
|
35
|
+
struct { uint32_t ufd; } fork;
|
|
36
|
+
struct { uint64_t from, to, len; } remap;
|
|
37
|
+
struct { uint64_t start, end; } remove;
|
|
38
|
+
struct { uint64_t reserved1, reserved2, reserved3; } reserved;
|
|
39
|
+
} arg;
|
|
40
|
+
} __attribute__((packed));
|
|
41
|
+
|
|
42
|
+
# define UFFD_EVENT_PAGEFAULT 0x12
|
|
43
|
+
# define UFFD_EVENT_FORK 0x13
|
|
44
|
+
# define UFFD_EVENT_REMAP 0x14
|
|
45
|
+
# define UFFD_EVENT_REMOVE 0x15
|
|
46
|
+
# define UFFD_EVENT_UNMAP 0x16
|
|
47
|
+
|
|
48
|
+
# define UFFD_PAGEFAULT_FLAG_WRITE (1 << 0)
|
|
49
|
+
# define UFFD_PAGEFAULT_FLAG_WP (1 << 1)
|
|
50
|
+
# define UFFD_PAGEFAULT_FLAG_MINOR (1 << 2)
|
|
51
|
+
|
|
52
|
+
struct uffdio_api { uint64_t api, features, ioctls; };
|
|
53
|
+
# define UFFD_FEATURE_PAGEFAULT_FLAG_WP (UINT64_C(1) << 0)
|
|
54
|
+
# define UFFD_FEATURE_EVENT_FORK (UINT64_C(1) << 1)
|
|
55
|
+
# define UFFD_FEATURE_EVENT_REMAP (UINT64_C(1) << 2)
|
|
56
|
+
# define UFFD_FEATURE_EVENT_REMOVE (UINT64_C(1) << 3)
|
|
57
|
+
# define UFFD_FEATURE_MISSING_HUGETLBFS (UINT64_C(1) << 4)
|
|
58
|
+
# define UFFD_FEATURE_MISSING_SHMEM (UINT64_C(1) << 5)
|
|
59
|
+
# define UFFD_FEATURE_EVENT_UNMAP (UINT64_C(1) << 6)
|
|
60
|
+
# define UFFD_FEATURE_SIGBUS (UINT64_C(1) << 7)
|
|
61
|
+
# define UFFD_FEATURE_THREAD_ID (UINT64_C(1) << 8)
|
|
62
|
+
|
|
63
|
+
struct uffdio_range { uint64_t start, len; };
|
|
64
|
+
struct uffdio_register {
|
|
65
|
+
struct uffdio_range range;
|
|
66
|
+
uint64_t mode, ioctls;
|
|
67
|
+
};
|
|
68
|
+
# define UFFDIO_REGISTER_MODE_MISSING (UINT64_C(1) << 0)
|
|
69
|
+
# define UFFDIO_REGISTER_MODE_WP (UINT64_C(1) << 1)
|
|
70
|
+
# define UFFDIO_REGISTER_MODE_MINOR (UINT64_C(1) << 2)
|
|
71
|
+
|
|
72
|
+
struct uffdio_copy {
|
|
73
|
+
uint64_t dst, src, len, mode;
|
|
74
|
+
int64_t copy;
|
|
75
|
+
};
|
|
76
|
+
# define UFFDIO_COPY_MODE_DONTWAKE (UINT64_C(1) << 0)
|
|
77
|
+
|
|
78
|
+
struct uffdio_zeropage {
|
|
79
|
+
struct uffdio_range range;
|
|
80
|
+
uint64_t mode;
|
|
81
|
+
int64_t zeropage;
|
|
82
|
+
};
|
|
83
|
+
# define UFFDIO_ZEROPAGE_MODE_DONTWAKE (UINT64_C(1) << 0)
|
|
84
|
+
|
|
85
|
+
struct uffdio_writeprotect {
|
|
86
|
+
struct uffdio_range range;
|
|
87
|
+
uint64_t mode;
|
|
88
|
+
};
|
|
89
|
+
# define UFFDIO_WRITEPROTECT_MODE_WP (UINT64_C(1) << 0)
|
|
90
|
+
|
|
91
|
+
struct uffdio_continue {
|
|
92
|
+
struct uffdio_range range;
|
|
93
|
+
uint64_t mode;
|
|
94
|
+
int64_t mapped;
|
|
95
|
+
};
|
|
96
|
+
# define UFFDIO_CONTINUE_MODE_DONTWAKE (UINT64_C(1) << 0)
|
|
97
|
+
|
|
98
|
+
# define UFFDIO_API _IOWR(UFFDIO, _UFFDIO_API, struct uffdio_api)
|
|
99
|
+
# define UFFDIO_REGISTER _IOWR(UFFDIO, _UFFDIO_REGISTER, struct uffdio_register)
|
|
100
|
+
# define UFFDIO_UNREGISTER _IOR(UFFDIO, _UFFDIO_UNREGISTER, struct uffdio_range)
|
|
101
|
+
# define UFFDIO_WAKE _IOR(UFFDIO, _UFFDIO_WAKE, struct uffdio_range)
|
|
102
|
+
# define UFFDIO_COPY _IOWR(UFFDIO, _UFFDIO_COPY, struct uffdio_copy)
|
|
103
|
+
# define UFFDIO_ZEROPAGE _IOWR(UFFDIO, _UFFDIO_ZEROPAGE, struct uffdio_zeropage)
|
|
104
|
+
# define UFFDIO_WRITEPROTECT _IOWR(UFFDIO, _UFFDIO_WRITEPROTECT, struct uffdio_writeprotect)
|
|
105
|
+
# define UFFDIO_CONTINUE _IOWR(UFFDIO, _UFFDIO_CONTINUE, struct uffdio_continue)
|
|
106
|
+
# endif
|
|
107
|
+
|
|
108
|
+
# ifndef UFFD_USER_MODE_ONLY
|
|
109
|
+
# define UFFD_USER_MODE_ONLY 1
|
|
110
|
+
# endif
|
|
111
|
+
|
|
112
|
+
/* Keep old or absent build headers feature-complete; the kernel still probes support. */
|
|
113
|
+
# ifndef UFFD_PAGEFAULT_FLAG_WP
|
|
114
|
+
# define UFFD_PAGEFAULT_FLAG_WP (1 << 1)
|
|
115
|
+
# endif
|
|
116
|
+
# ifndef UFFD_PAGEFAULT_FLAG_MINOR
|
|
117
|
+
# define UFFD_PAGEFAULT_FLAG_MINOR (1 << 2)
|
|
118
|
+
# endif
|
|
119
|
+
# ifndef UFFD_FEATURE_PAGEFAULT_FLAG_WP
|
|
120
|
+
# define UFFD_FEATURE_PAGEFAULT_FLAG_WP (UINT64_C(1) << 0)
|
|
121
|
+
# endif
|
|
122
|
+
# ifndef UFFD_FEATURE_EVENT_FORK
|
|
123
|
+
# define UFFD_FEATURE_EVENT_FORK (UINT64_C(1) << 1)
|
|
124
|
+
# endif
|
|
125
|
+
# ifndef UFFD_FEATURE_EVENT_REMAP
|
|
126
|
+
# define UFFD_FEATURE_EVENT_REMAP (UINT64_C(1) << 2)
|
|
127
|
+
# endif
|
|
128
|
+
# ifndef UFFD_FEATURE_EVENT_REMOVE
|
|
129
|
+
# define UFFD_FEATURE_EVENT_REMOVE (UINT64_C(1) << 3)
|
|
130
|
+
# endif
|
|
131
|
+
# ifndef UFFD_FEATURE_MISSING_HUGETLBFS
|
|
132
|
+
# define UFFD_FEATURE_MISSING_HUGETLBFS (UINT64_C(1) << 4)
|
|
133
|
+
# endif
|
|
134
|
+
# ifndef UFFD_FEATURE_MISSING_SHMEM
|
|
135
|
+
# define UFFD_FEATURE_MISSING_SHMEM (UINT64_C(1) << 5)
|
|
136
|
+
# endif
|
|
137
|
+
# ifndef UFFD_FEATURE_EVENT_UNMAP
|
|
138
|
+
# define UFFD_FEATURE_EVENT_UNMAP (UINT64_C(1) << 6)
|
|
139
|
+
# endif
|
|
140
|
+
# ifndef UFFD_FEATURE_SIGBUS
|
|
141
|
+
# define UFFD_FEATURE_SIGBUS (UINT64_C(1) << 7)
|
|
142
|
+
# endif
|
|
143
|
+
# ifndef UFFD_FEATURE_THREAD_ID
|
|
144
|
+
# define UFFD_FEATURE_THREAD_ID (UINT64_C(1) << 8)
|
|
145
|
+
# endif
|
|
146
|
+
# ifndef UFFD_FEATURE_MINOR_HUGETLBFS
|
|
147
|
+
# define UFFD_FEATURE_MINOR_HUGETLBFS (UINT64_C(1) << 9)
|
|
148
|
+
# endif
|
|
149
|
+
# ifndef UFFD_FEATURE_MINOR_SHMEM
|
|
150
|
+
# define UFFD_FEATURE_MINOR_SHMEM (UINT64_C(1) << 10)
|
|
151
|
+
# endif
|
|
152
|
+
# ifndef UFFD_FEATURE_EXACT_ADDRESS
|
|
153
|
+
# define UFFD_FEATURE_EXACT_ADDRESS (UINT64_C(1) << 11)
|
|
154
|
+
# endif
|
|
155
|
+
# ifndef UFFD_FEATURE_WP_HUGETLBFS_SHMEM
|
|
156
|
+
# define UFFD_FEATURE_WP_HUGETLBFS_SHMEM (UINT64_C(1) << 12)
|
|
157
|
+
# endif
|
|
158
|
+
# ifndef UFFD_FEATURE_WP_UNPOPULATED
|
|
159
|
+
# define UFFD_FEATURE_WP_UNPOPULATED (UINT64_C(1) << 13)
|
|
160
|
+
# endif
|
|
161
|
+
# ifndef UFFD_FEATURE_POISON
|
|
162
|
+
# define UFFD_FEATURE_POISON (UINT64_C(1) << 14)
|
|
163
|
+
# endif
|
|
164
|
+
# ifndef UFFD_FEATURE_WP_ASYNC
|
|
165
|
+
# define UFFD_FEATURE_WP_ASYNC (UINT64_C(1) << 15)
|
|
166
|
+
# endif
|
|
167
|
+
# ifndef UFFD_FEATURE_MOVE
|
|
168
|
+
# define UFFD_FEATURE_MOVE (UINT64_C(1) << 16)
|
|
169
|
+
# endif
|
|
170
|
+
|
|
171
|
+
# ifndef UFFDIO_REGISTER_MODE_WP
|
|
172
|
+
# define UFFDIO_REGISTER_MODE_WP (UINT64_C(1) << 1)
|
|
173
|
+
# endif
|
|
174
|
+
# ifndef UFFDIO_REGISTER_MODE_MINOR
|
|
175
|
+
# define UFFDIO_REGISTER_MODE_MINOR (UINT64_C(1) << 2)
|
|
176
|
+
# endif
|
|
177
|
+
|
|
178
|
+
# ifndef UFFDIO_WRITEPROTECT
|
|
179
|
+
# ifndef _UFFDIO_WRITEPROTECT
|
|
180
|
+
# define _UFFDIO_WRITEPROTECT 0x06
|
|
181
|
+
# endif
|
|
182
|
+
struct uffdio_writeprotect {
|
|
183
|
+
struct uffdio_range range;
|
|
184
|
+
uint64_t mode;
|
|
185
|
+
};
|
|
186
|
+
# define UFFDIO_WRITEPROTECT_MODE_WP (UINT64_C(1) << 0)
|
|
187
|
+
# define UFFDIO_WRITEPROTECT _IOWR(UFFDIO, _UFFDIO_WRITEPROTECT, struct uffdio_writeprotect)
|
|
188
|
+
# endif
|
|
189
|
+
|
|
190
|
+
# ifndef UFFDIO_CONTINUE
|
|
191
|
+
# ifndef _UFFDIO_CONTINUE
|
|
192
|
+
# define _UFFDIO_CONTINUE 0x07
|
|
193
|
+
# endif
|
|
194
|
+
struct uffdio_continue {
|
|
195
|
+
struct uffdio_range range;
|
|
196
|
+
uint64_t mode;
|
|
197
|
+
int64_t mapped;
|
|
198
|
+
};
|
|
199
|
+
# define UFFDIO_CONTINUE_MODE_DONTWAKE (UINT64_C(1) << 0)
|
|
200
|
+
# define UFFDIO_CONTINUE _IOWR(UFFDIO, _UFFDIO_CONTINUE, struct uffdio_continue)
|
|
201
|
+
# endif
|
|
202
|
+
|
|
203
|
+
# ifndef UFFDIO_POISON
|
|
204
|
+
# ifndef _UFFDIO_POISON
|
|
205
|
+
# define _UFFDIO_POISON 0x08
|
|
206
|
+
# endif
|
|
207
|
+
struct uffdio_poison {
|
|
208
|
+
struct uffdio_range range;
|
|
209
|
+
uint64_t mode;
|
|
210
|
+
int64_t updated;
|
|
211
|
+
};
|
|
212
|
+
# define UFFDIO_POISON_MODE_DONTWAKE (UINT64_C(1) << 0)
|
|
213
|
+
# define UFFDIO_POISON _IOWR(UFFDIO, _UFFDIO_POISON, struct uffdio_poison)
|
|
214
|
+
# endif
|
|
215
|
+
|
|
216
|
+
# ifndef UFFDIO_MOVE
|
|
217
|
+
# ifndef _UFFDIO_MOVE
|
|
218
|
+
# define _UFFDIO_MOVE 0x05
|
|
219
|
+
# endif
|
|
220
|
+
struct uffdio_move {
|
|
221
|
+
uint64_t dst, src, len, mode;
|
|
222
|
+
int64_t move;
|
|
223
|
+
};
|
|
224
|
+
# define UFFDIO_MOVE_MODE_DONTWAKE (UINT64_C(1) << 0)
|
|
225
|
+
# define UFFDIO_MOVE _IOWR(UFFDIO, _UFFDIO_MOVE, struct uffdio_move)
|
|
226
|
+
# endif
|
|
227
|
+
|
|
228
|
+
/* Added in Linux 5.19. Old headers still build and use the syscall path. */
|
|
229
|
+
# ifndef USERFAULTFD_IOC
|
|
230
|
+
# define USERFAULTFD_IOC 0xAA
|
|
231
|
+
# endif
|
|
232
|
+
# ifndef USERFAULTFD_IOC_NEW
|
|
233
|
+
# define USERFAULTFD_IOC_NEW _IO(USERFAULTFD_IOC, 0x00)
|
|
234
|
+
# endif
|
|
235
|
+
#endif
|
|
236
|
+
|
|
237
|
+
#endif
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/* Generated by rake gen:constants. Do not edit. */
|
|
2
|
+
#include "ruby.h"
|
|
3
|
+
#include "extconf.h"
|
|
4
|
+
|
|
5
|
+
#ifdef __linux__
|
|
6
|
+
# include "compat.h"
|
|
7
|
+
# define UFFD_CONST(name) rb_define_const(klass, #name, ULL2NUM(name))
|
|
8
|
+
#else
|
|
9
|
+
# define UFFD_CONST(name) ((void)0)
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
void
|
|
13
|
+
userfaultfd_define_constants(VALUE klass)
|
|
14
|
+
{
|
|
15
|
+
#ifdef UFFD_API
|
|
16
|
+
UFFD_CONST(UFFD_API);
|
|
17
|
+
#endif
|
|
18
|
+
#ifdef UFFD_EVENT_PAGEFAULT
|
|
19
|
+
UFFD_CONST(UFFD_EVENT_PAGEFAULT);
|
|
20
|
+
#endif
|
|
21
|
+
#ifdef UFFD_EVENT_FORK
|
|
22
|
+
UFFD_CONST(UFFD_EVENT_FORK);
|
|
23
|
+
#endif
|
|
24
|
+
#ifdef UFFD_EVENT_REMAP
|
|
25
|
+
UFFD_CONST(UFFD_EVENT_REMAP);
|
|
26
|
+
#endif
|
|
27
|
+
#ifdef UFFD_EVENT_REMOVE
|
|
28
|
+
UFFD_CONST(UFFD_EVENT_REMOVE);
|
|
29
|
+
#endif
|
|
30
|
+
#ifdef UFFD_EVENT_UNMAP
|
|
31
|
+
UFFD_CONST(UFFD_EVENT_UNMAP);
|
|
32
|
+
#endif
|
|
33
|
+
#ifdef UFFD_PAGEFAULT_FLAG_WRITE
|
|
34
|
+
UFFD_CONST(UFFD_PAGEFAULT_FLAG_WRITE);
|
|
35
|
+
#endif
|
|
36
|
+
#ifdef UFFD_PAGEFAULT_FLAG_WP
|
|
37
|
+
UFFD_CONST(UFFD_PAGEFAULT_FLAG_WP);
|
|
38
|
+
#endif
|
|
39
|
+
#ifdef UFFD_PAGEFAULT_FLAG_MINOR
|
|
40
|
+
UFFD_CONST(UFFD_PAGEFAULT_FLAG_MINOR);
|
|
41
|
+
#endif
|
|
42
|
+
#ifdef UFFD_FEATURE_PAGEFAULT_FLAG_WP
|
|
43
|
+
UFFD_CONST(UFFD_FEATURE_PAGEFAULT_FLAG_WP);
|
|
44
|
+
#endif
|
|
45
|
+
#ifdef UFFD_FEATURE_EVENT_FORK
|
|
46
|
+
UFFD_CONST(UFFD_FEATURE_EVENT_FORK);
|
|
47
|
+
#endif
|
|
48
|
+
#ifdef UFFD_FEATURE_EVENT_REMAP
|
|
49
|
+
UFFD_CONST(UFFD_FEATURE_EVENT_REMAP);
|
|
50
|
+
#endif
|
|
51
|
+
#ifdef UFFD_FEATURE_EVENT_REMOVE
|
|
52
|
+
UFFD_CONST(UFFD_FEATURE_EVENT_REMOVE);
|
|
53
|
+
#endif
|
|
54
|
+
#ifdef UFFD_FEATURE_EVENT_UNMAP
|
|
55
|
+
UFFD_CONST(UFFD_FEATURE_EVENT_UNMAP);
|
|
56
|
+
#endif
|
|
57
|
+
#ifdef UFFD_FEATURE_MISSING_HUGETLBFS
|
|
58
|
+
UFFD_CONST(UFFD_FEATURE_MISSING_HUGETLBFS);
|
|
59
|
+
#endif
|
|
60
|
+
#ifdef UFFD_FEATURE_MISSING_SHMEM
|
|
61
|
+
UFFD_CONST(UFFD_FEATURE_MISSING_SHMEM);
|
|
62
|
+
#endif
|
|
63
|
+
#ifdef UFFD_FEATURE_SIGBUS
|
|
64
|
+
UFFD_CONST(UFFD_FEATURE_SIGBUS);
|
|
65
|
+
#endif
|
|
66
|
+
#ifdef UFFD_FEATURE_THREAD_ID
|
|
67
|
+
UFFD_CONST(UFFD_FEATURE_THREAD_ID);
|
|
68
|
+
#endif
|
|
69
|
+
#ifdef UFFD_FEATURE_MINOR_HUGETLBFS
|
|
70
|
+
UFFD_CONST(UFFD_FEATURE_MINOR_HUGETLBFS);
|
|
71
|
+
#endif
|
|
72
|
+
#ifdef UFFD_FEATURE_MINOR_SHMEM
|
|
73
|
+
UFFD_CONST(UFFD_FEATURE_MINOR_SHMEM);
|
|
74
|
+
#endif
|
|
75
|
+
#ifdef UFFD_FEATURE_EXACT_ADDRESS
|
|
76
|
+
UFFD_CONST(UFFD_FEATURE_EXACT_ADDRESS);
|
|
77
|
+
#endif
|
|
78
|
+
#ifdef UFFD_FEATURE_WP_HUGETLBFS_SHMEM
|
|
79
|
+
UFFD_CONST(UFFD_FEATURE_WP_HUGETLBFS_SHMEM);
|
|
80
|
+
#endif
|
|
81
|
+
#ifdef UFFD_FEATURE_WP_UNPOPULATED
|
|
82
|
+
UFFD_CONST(UFFD_FEATURE_WP_UNPOPULATED);
|
|
83
|
+
#endif
|
|
84
|
+
#ifdef UFFD_FEATURE_POISON
|
|
85
|
+
UFFD_CONST(UFFD_FEATURE_POISON);
|
|
86
|
+
#endif
|
|
87
|
+
#ifdef UFFD_FEATURE_WP_ASYNC
|
|
88
|
+
UFFD_CONST(UFFD_FEATURE_WP_ASYNC);
|
|
89
|
+
#endif
|
|
90
|
+
#ifdef UFFD_FEATURE_MOVE
|
|
91
|
+
UFFD_CONST(UFFD_FEATURE_MOVE);
|
|
92
|
+
#endif
|
|
93
|
+
#ifdef UFFDIO_REGISTER_MODE_MISSING
|
|
94
|
+
UFFD_CONST(UFFDIO_REGISTER_MODE_MISSING);
|
|
95
|
+
#endif
|
|
96
|
+
#ifdef UFFDIO_REGISTER_MODE_WP
|
|
97
|
+
UFFD_CONST(UFFDIO_REGISTER_MODE_WP);
|
|
98
|
+
#endif
|
|
99
|
+
#ifdef UFFDIO_REGISTER_MODE_MINOR
|
|
100
|
+
UFFD_CONST(UFFDIO_REGISTER_MODE_MINOR);
|
|
101
|
+
#endif
|
|
102
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mkmf"
|
|
4
|
+
|
|
5
|
+
have_header("linux/userfaultfd.h")
|
|
6
|
+
have_header("sys/ioctl.h")
|
|
7
|
+
have_header("sys/syscall.h")
|
|
8
|
+
have_func("syscall", "unistd.h")
|
|
9
|
+
have_library("pthread")
|
|
10
|
+
|
|
11
|
+
create_header
|
|
12
|
+
create_makefile("userfaultfd/userfaultfd")
|