ublk 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 +148 -0
- data/Rakefile +42 -0
- data/examples/encrypted.rb +44 -0
- data/examples/fault_injection.rb +31 -0
- data/examples/http_disk.rb +26 -0
- data/examples/loop.rb +21 -0
- data/examples/ramdisk.rb +26 -0
- data/examples/tracing.rb +25 -0
- data/ext/ublk/compat.h +188 -0
- data/ext/ublk/constants.c +18 -0
- data/ext/ublk/control.c +292 -0
- data/ext/ublk/extconf.rb +16 -0
- data/ext/ublk/internal.h +43 -0
- data/ext/ublk/server.c +413 -0
- data/ext/ublk/ublk.c +34 -0
- data/lib/ublk/control.rb +46 -0
- data/lib/ublk/device.rb +182 -0
- data/lib/ublk/device_info.rb +14 -0
- data/lib/ublk/params.rb +35 -0
- data/lib/ublk/target.rb +42 -0
- data/lib/ublk/version.rb +5 -0
- data/lib/ublk.rb +36 -0
- data/sig/ublk.rbs +96 -0
- data/tools/bench.rb +34 -0
- data/tools/dump_constants.c +19 -0
- data/tools/vm/Makefile +5 -0
- data/tools/vm/config-fragment +4 -0
- data/tools/vm/guest-test.sh +6 -0
- data/tools/vm/run.sh +5 -0
- metadata +75 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bba0d82a93398079b7ed866556fcc9e6b6d7c88f16a6afcf0c254bd4b18d8a5b
|
|
4
|
+
data.tar.gz: 52f2b58dd1fae0a22fbfe1d2bffd28f139f4e8cc34b1f6f7b22a040b92f01052
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b8d17740f6692d5cbd4a0f159c3f715348e909daf499cf1e899d1fc426a5fcf20f002186744fda7d7f22ea3eb69f734b7a2c050c687ca206e5164b1808e45f84
|
|
7
|
+
data.tar.gz: 23c5cf0c070cd73825935f7531bf43e2e3d9b5e08f93875a14b9c24fb1544887d608b3050532af464d5fd143163ff48c947a1f25223424b5e0908255851e7002
|
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,148 @@
|
|
|
1
|
+
# UBLK
|
|
2
|
+
|
|
3
|
+
Write Linux block-device targets in Ruby. UBLK uses a C extension, liburing,
|
|
4
|
+
and the kernel's `UBLK_F_USER_COPY` interface so target implementations only
|
|
5
|
+
exchange Ruby strings.
|
|
6
|
+
|
|
7
|
+
> [!WARNING]
|
|
8
|
+
> This is experimental. Never use it for a root filesystem, swap, or data you
|
|
9
|
+
> cannot recreate. A Ruby GC pause stops device I/O, and a crashed server makes
|
|
10
|
+
> outstanding I/O fail unless recovery was enabled.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- Linux 6.6 or newer with `CONFIG_BLK_DEV_UBLK`
|
|
15
|
+
- `/dev/ublk-control` and normally `CAP_SYS_ADMIN`
|
|
16
|
+
- Ruby 3.2 or newer
|
|
17
|
+
- liburing headers (`liburing-dev` on Debian/Ubuntu, `liburing-devel` on Fedora)
|
|
18
|
+
|
|
19
|
+
The gem deliberately fails to build without liburing. Once installed,
|
|
20
|
+
`require "ublk"` remains safe on unsupported hosts and `UBLK.supported?`
|
|
21
|
+
reports whether the running kernel exposes USER_COPY.
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
bundle install
|
|
25
|
+
bundle exec rake compile
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## RAM disk
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
require "ublk"
|
|
32
|
+
|
|
33
|
+
class RamDisk < UBLK::Target
|
|
34
|
+
def initialize(size)
|
|
35
|
+
@data = "\0".b * size
|
|
36
|
+
super(size:)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def read(offset, length) = @data.byteslice(offset, length)
|
|
40
|
+
|
|
41
|
+
def write(offset, data)
|
|
42
|
+
@data[offset, data.bytesize] = data
|
|
43
|
+
data.bytesize
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
device = UBLK::Device.create(RamDisk.new(256 * 1024 * 1024))
|
|
48
|
+
puts device.path
|
|
49
|
+
device.run
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`run` blocks until another thread calls `stop` or the process receives an
|
|
53
|
+
interrupt. For a background server, call `start`, use `device.path`, then call
|
|
54
|
+
`delete`. Created devices are also deleted by an `at_exit` hook.
|
|
55
|
+
|
|
56
|
+
Targets must implement `read` and `write`. `flush` succeeds by default;
|
|
57
|
+
`discard` and `write_zeroes` default to `EOPNOTSUPP`. Raising an `Errno::*`
|
|
58
|
+
exception returns that errno to the kernel; other exceptions become `EIO`.
|
|
59
|
+
|
|
60
|
+
`Device.create` locks current and future memory by default to avoid a paging
|
|
61
|
+
deadlock. Pass `mlock: false` only for disposable development devices. Multiple
|
|
62
|
+
hardware queues use one Ruby thread each, but callbacks remain serialized by
|
|
63
|
+
the GVL.
|
|
64
|
+
|
|
65
|
+
See `examples/` for RAM, file-backed, HTTP read-only, encrypted, tracing, and
|
|
66
|
+
fault-injection targets.
|
|
67
|
+
|
|
68
|
+
## Recovery
|
|
69
|
+
|
|
70
|
+
Create with `recovery: true`, then reconnect after a server restart:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
device = UBLK::Device.recover(target, id: 0)
|
|
74
|
+
device.run
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Outstanding requests fail during this v1 recovery mode; they are not reissued.
|
|
78
|
+
|
|
79
|
+
## Unsupported environments
|
|
80
|
+
|
|
81
|
+
- macOS and Windows: no Linux ublk driver
|
|
82
|
+
- Docker Desktop: its VM normally does not expose `/dev/ublk-control`
|
|
83
|
+
- containers without the host ublk device and the required capability
|
|
84
|
+
- WSL2 kernels without `CONFIG_BLK_DEV_UBLK`
|
|
85
|
+
- Linux before 6.6: no required USER_COPY interface
|
|
86
|
+
- standard GitHub-hosted jobs: no usable privileged ublk device
|
|
87
|
+
|
|
88
|
+
## Tests
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
bundle exec rake compile
|
|
92
|
+
bundle exec rake test:unit
|
|
93
|
+
bundle exec rbs validate
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The destructive system suite only runs when explicitly enabled inside a VM.
|
|
97
|
+
It covers 64 MiB raw reads, maximum-I/O boundary crossing, ext4 and fsck,
|
|
98
|
+
flush/discard callbacks, errno propagation, device deletion, SIGKILL recovery,
|
|
99
|
+
four-way verified fio, a one-minute RSS leak check, and 1,000 control opens.
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
tools/vm/run.sh v6.6
|
|
103
|
+
tools/vm/run.sh v6.12
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Run 4K random-read, random-write, and sequential-read fio workloads in the
|
|
107
|
+
same VM:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
UBLK_BENCH=1 bundle exec ruby tools/bench.rb
|
|
111
|
+
UBLK_BENCH=1 UBLK_BENCH_GC_STRESS=1 bundle exec ruby tools/bench.rb
|
|
112
|
+
UBLK_BENCH=1 UBLK_BENCH_DEVICE=/dev/ublkb0 bundle exec ruby tools/bench.rb
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The last form measures an externally started device, such as `ublksrv`'s C
|
|
116
|
+
loop target, with the identical fio arguments. fio's JSON includes IOPS and
|
|
117
|
+
p99 latency. No hardware-independent number is claimed: CPU, Ruby, GC, and VM
|
|
118
|
+
acceleration materially change the result, and Ruby callbacks remain bounded
|
|
119
|
+
by the GVL.
|
|
120
|
+
|
|
121
|
+
### Reference benchmark
|
|
122
|
+
|
|
123
|
+
One reproducible comparison was run on Linux 6.12.0 under QEMU TCG (2 vCPUs,
|
|
124
|
+
1.5 GiB RAM), using Ruby 3.2, fio 3.36, and upstream ublksrv 1.6.1
|
|
125
|
+
(`abbfea2b5918`). Both loop targets used a 256 MiB sparse file on `/tmp`, one
|
|
126
|
+
queue of depth 128, and buffered backing-file I/O. fio used its io_uring engine,
|
|
127
|
+
4 KiB direct I/O, iodepth 32, and one 30-second run per workload.
|
|
128
|
+
|
|
129
|
+
| Target | Workload | IOPS | completion p99 |
|
|
130
|
+
|---|---:|---:|---:|
|
|
131
|
+
| Ruby loop | random read | 33,774 | 2.900 ms |
|
|
132
|
+
| Ruby loop | random write | 33,896 | 2.376 ms |
|
|
133
|
+
| Ruby loop | sequential read | 26,801 | 3.850 ms |
|
|
134
|
+
| C ublksrv loop | random read | 64,213 | 0.938 ms |
|
|
135
|
+
| C ublksrv loop | random write | 56,456 | 1.090 ms |
|
|
136
|
+
| C ublksrv loop | sequential read | 62,011 | 0.987 ms |
|
|
137
|
+
| Ruby loop, `GC.stress` | random read | 9.72 | 6.610 s |
|
|
138
|
+
| Ruby loop, `GC.stress` | random write | 12.61 | 4.178 s |
|
|
139
|
+
| Ruby loop, `GC.stress` | sequential read | 7.42 | 7.952 s |
|
|
140
|
+
|
|
141
|
+
These are diagnostic VM results, not production performance claims. In this
|
|
142
|
+
run the C loop target delivered roughly 1.7–2.3 times the IOPS of normal Ruby,
|
|
143
|
+
while `GC.stress` demonstrated that stop-the-world GC can make latency
|
|
144
|
+
unacceptable for block storage.
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
data/Rakefile
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "rake/extensiontask"
|
|
6
|
+
require "rspec/core/rake_task"
|
|
7
|
+
require "yard"
|
|
8
|
+
|
|
9
|
+
Rake::ExtensionTask.new("ublk") do |ext|
|
|
10
|
+
ext.lib_dir = "lib/ublk"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
YARD::Rake::YardocTask.new
|
|
14
|
+
|
|
15
|
+
namespace :test do
|
|
16
|
+
RSpec::Core::RakeTask.new(:unit) { |task| task.pattern = "spec/unit/**/*_spec.rb" }
|
|
17
|
+
RSpec::Core::RakeTask.new(:system) { |task| task.pattern = "spec/system/**/*_spec.rb" }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
namespace :gen do
|
|
21
|
+
desc "Regenerate Ruby constants from the installed Linux ublk headers"
|
|
22
|
+
task :constants do
|
|
23
|
+
executable = "tmp/dump_ublk_constants"
|
|
24
|
+
FileUtils.mkdir_p("tmp")
|
|
25
|
+
sh RbConfig::CONFIG.fetch("CC"), "-Iext/ublk", "tools/dump_constants.c", "-o", executable
|
|
26
|
+
names = IO.popen([executable], &:read).lines.map { |line| line.split("=", 2).first }
|
|
27
|
+
body = names.map { |name| " UBLK_CONST(#{name});" }.join("\n")
|
|
28
|
+
File.write("ext/ublk/constants.c", <<~C)
|
|
29
|
+
/* Generated by rake gen:constants. */
|
|
30
|
+
#include "internal.h"
|
|
31
|
+
|
|
32
|
+
#define UBLK_CONST(name) rb_define_const(ublk_native_module, #name, ULL2NUM(name))
|
|
33
|
+
|
|
34
|
+
void ublk_init_constants(void)
|
|
35
|
+
{
|
|
36
|
+
#{body}
|
|
37
|
+
}
|
|
38
|
+
C
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
task default: "test:unit"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
require "ublk"
|
|
5
|
+
|
|
6
|
+
class EncryptedDisk < UBLK::Target
|
|
7
|
+
def initialize(path, size:, key:)
|
|
8
|
+
raise ArgumentError, "key must be 32 bytes" unless key.bytesize == 32
|
|
9
|
+
|
|
10
|
+
@file = File.open(path, File::RDWR | File::CREAT, 0o600)
|
|
11
|
+
@file.truncate(size)
|
|
12
|
+
@key = key
|
|
13
|
+
super(size:)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def read(offset, length) = crypt(offset, @file.pread(length, offset))
|
|
17
|
+
|
|
18
|
+
def write(offset, data)
|
|
19
|
+
@file.pwrite(crypt(offset, data), offset)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def flush = @file.fsync
|
|
23
|
+
|
|
24
|
+
def discard(offset, length)
|
|
25
|
+
write(offset, "\0".b * length)
|
|
26
|
+
0
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
alias write_zeroes discard
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def crypt(offset, data)
|
|
34
|
+
cipher = OpenSSL::Cipher.new("aes-256-ctr")
|
|
35
|
+
cipher.encrypt
|
|
36
|
+
cipher.key = @key
|
|
37
|
+
cipher.iv = [0, offset / 16].pack("Q>Q>")
|
|
38
|
+
cipher.update(data) + cipher.final
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
abort "usage: UBLK_KEY_HEX=<64 hex chars> #{$PROGRAM_NAME} FILE SIZE" unless ARGV.size == 2 && ENV["UBLK_KEY_HEX"]
|
|
43
|
+
key = [ENV.fetch("UBLK_KEY_HEX")].pack("H*")
|
|
44
|
+
UBLK::Device.create(EncryptedDisk.new(ARGV.fetch(0), size: Integer(ARGV.fetch(1), 10), key:)).run
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ramdisk"
|
|
4
|
+
|
|
5
|
+
class FaultInjectionDisk < RamDisk
|
|
6
|
+
def initialize(size, failing_range)
|
|
7
|
+
@failing_range = failing_range
|
|
8
|
+
super(size)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def read(offset, length)
|
|
12
|
+
fail_if_needed(offset, length)
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def write(offset, data)
|
|
17
|
+
fail_if_needed(offset, data.bytesize)
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def fail_if_needed(offset, length)
|
|
24
|
+
raise Errno::EIO if offset < @failing_range.end && offset + length > @failing_range.begin
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if $PROGRAM_NAME == __FILE__
|
|
29
|
+
disk = FaultInjectionDisk.new(256 * 1024 * 1024, (64 * 1024 * 1024)...(65 * 1024 * 1024))
|
|
30
|
+
UBLK::Device.create(disk).run
|
|
31
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "ublk"
|
|
5
|
+
|
|
6
|
+
class HTTPDisk < UBLK::Target
|
|
7
|
+
def initialize(url)
|
|
8
|
+
@uri = URI(url)
|
|
9
|
+
response = Net::HTTP.start(@uri.host, @uri.port, use_ssl: @uri.scheme == "https") { |http| http.head(@uri.request_uri) }
|
|
10
|
+
super(size: Integer(response.fetch("content-length"), 10))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def read(offset, length)
|
|
14
|
+
request = Net::HTTP::Get.new(@uri)
|
|
15
|
+
request["Range"] = "bytes=#{offset}-#{offset + length - 1}"
|
|
16
|
+
response = Net::HTTP.start(@uri.host, @uri.port, use_ssl: @uri.scheme == "https") { |http| http.request(request) }
|
|
17
|
+
raise Errno::EIO, "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
|
|
18
|
+
|
|
19
|
+
response.body
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def read_only? = true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
abort "usage: #{$PROGRAM_NAME} URL" unless ARGV.one?
|
|
26
|
+
UBLK::Device.create(HTTPDisk.new(ARGV.fetch(0))).run
|
data/examples/loop.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ublk"
|
|
4
|
+
|
|
5
|
+
class LoopTarget < UBLK::Target
|
|
6
|
+
def initialize(path, size: File.size(path), read_only: false)
|
|
7
|
+
@file = File.open(path, read_only ? "rb" : "r+b")
|
|
8
|
+
@read_only = read_only
|
|
9
|
+
super(size:)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def read(offset, length) = @file.pread(length, offset)
|
|
13
|
+
def write(offset, data) = @file.pwrite(data, offset)
|
|
14
|
+
def flush = @file.fsync
|
|
15
|
+
def read_only? = @read_only
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if $PROGRAM_NAME == __FILE__
|
|
19
|
+
abort "usage: #{$PROGRAM_NAME} IMAGE" unless ARGV.one?
|
|
20
|
+
UBLK::Device.create(LoopTarget.new(ARGV.fetch(0))).run
|
|
21
|
+
end
|
data/examples/ramdisk.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ublk"
|
|
4
|
+
|
|
5
|
+
class RamDisk < UBLK::Target
|
|
6
|
+
def initialize(size)
|
|
7
|
+
@data = "\0".b * size
|
|
8
|
+
super(size:)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def read(offset, length) = @data.byteslice(offset, length)
|
|
12
|
+
|
|
13
|
+
def write(offset, data)
|
|
14
|
+
@data[offset, data.bytesize] = data
|
|
15
|
+
data.bytesize
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def discard(offset, length)
|
|
19
|
+
@data[offset, length] = "\0".b * length
|
|
20
|
+
0
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
alias write_zeroes discard
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
UBLK::Device.create(RamDisk.new(256 * 1024 * 1024)).run if $PROGRAM_NAME == __FILE__
|
data/examples/tracing.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ramdisk"
|
|
4
|
+
|
|
5
|
+
class TracingTarget < UBLK::Target
|
|
6
|
+
def initialize(target, output: $stderr)
|
|
7
|
+
@target = target
|
|
8
|
+
@output = output
|
|
9
|
+
super(size: target.size, queues: target.queues, depth: target.depth)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
%i[read write discard write_zeroes].each do |operation|
|
|
13
|
+
define_method(operation) do |offset, value|
|
|
14
|
+
@output.puts("#{operation} offset=#{offset} length=#{value.respond_to?(:bytesize) ? value.bytesize : value}")
|
|
15
|
+
@target.public_send(operation, offset, value)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def flush
|
|
20
|
+
@output.puts("flush")
|
|
21
|
+
@target.flush
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
UBLK::Device.create(TracingTarget.new(RamDisk.new(256 * 1024 * 1024))).run
|
data/ext/ublk/compat.h
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#ifndef UBLK_COMPAT_H
|
|
2
|
+
#define UBLK_COMPAT_H
|
|
3
|
+
|
|
4
|
+
#include <linux/ioctl.h>
|
|
5
|
+
#include <linux/types.h>
|
|
6
|
+
#include <stddef.h>
|
|
7
|
+
|
|
8
|
+
#if !defined(UBLK_FORCE_COMPAT) && defined(__has_include)
|
|
9
|
+
#if __has_include(<linux/ublk_cmd.h>)
|
|
10
|
+
#include <linux/ublk_cmd.h>
|
|
11
|
+
#define UBLK_HAVE_UAPI_HEADER 1
|
|
12
|
+
#endif
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
#ifndef UBLK_HAVE_UAPI_HEADER
|
|
16
|
+
#define UBLK_CMD_GET_QUEUE_AFFINITY 0x01
|
|
17
|
+
#define UBLK_CMD_GET_DEV_INFO 0x02
|
|
18
|
+
#define UBLK_CMD_ADD_DEV 0x04
|
|
19
|
+
#define UBLK_CMD_DEL_DEV 0x05
|
|
20
|
+
#define UBLK_CMD_START_DEV 0x06
|
|
21
|
+
#define UBLK_CMD_STOP_DEV 0x07
|
|
22
|
+
#define UBLK_CMD_SET_PARAMS 0x08
|
|
23
|
+
#define UBLK_CMD_GET_PARAMS 0x09
|
|
24
|
+
#define UBLK_CMD_START_USER_RECOVERY 0x10
|
|
25
|
+
#define UBLK_CMD_END_USER_RECOVERY 0x11
|
|
26
|
+
#define UBLK_CMD_GET_DEV_INFO2 0x12
|
|
27
|
+
|
|
28
|
+
#define UBLK_IO_FETCH_REQ 0x20
|
|
29
|
+
#define UBLK_IO_COMMIT_AND_FETCH_REQ 0x21
|
|
30
|
+
#define UBLK_IO_NEED_GET_DATA 0x22
|
|
31
|
+
|
|
32
|
+
#define UBLK_F_USER_RECOVERY (1ULL << 3)
|
|
33
|
+
|
|
34
|
+
#define UBLK_IO_OP_READ 0
|
|
35
|
+
#define UBLK_IO_OP_WRITE 1
|
|
36
|
+
#define UBLK_IO_OP_FLUSH 2
|
|
37
|
+
#define UBLK_IO_OP_DISCARD 3
|
|
38
|
+
#define UBLK_IO_OP_WRITE_SAME 4
|
|
39
|
+
#define UBLK_IO_OP_WRITE_ZEROES 5
|
|
40
|
+
|
|
41
|
+
#define UBLK_ATTR_READ_ONLY (1U << 0)
|
|
42
|
+
#define UBLK_ATTR_ROTATIONAL (1U << 1)
|
|
43
|
+
#define UBLK_ATTR_VOLATILE_CACHE (1U << 2)
|
|
44
|
+
#define UBLK_ATTR_FUA (1U << 3)
|
|
45
|
+
#define UBLK_PARAM_TYPE_BASIC (1U << 0)
|
|
46
|
+
#define UBLK_PARAM_TYPE_DISCARD (1U << 1)
|
|
47
|
+
|
|
48
|
+
struct ublksrv_io_desc {
|
|
49
|
+
__u32 op_flags;
|
|
50
|
+
__u32 nr_sectors;
|
|
51
|
+
__u64 start_sector;
|
|
52
|
+
__u64 addr;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
struct ublksrv_io_cmd {
|
|
56
|
+
__u16 q_id;
|
|
57
|
+
__u16 tag;
|
|
58
|
+
__s32 result;
|
|
59
|
+
__u64 addr;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
struct ublk_param_basic {
|
|
63
|
+
__u32 attrs;
|
|
64
|
+
__u8 logical_bs_shift;
|
|
65
|
+
__u8 physical_bs_shift;
|
|
66
|
+
__u8 io_opt_shift;
|
|
67
|
+
__u8 io_min_shift;
|
|
68
|
+
__u32 max_sectors;
|
|
69
|
+
__u32 chunk_sectors;
|
|
70
|
+
__u64 dev_sectors;
|
|
71
|
+
__u64 virt_boundary_mask;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
struct ublk_param_discard {
|
|
75
|
+
__u32 discard_alignment;
|
|
76
|
+
__u32 discard_granularity;
|
|
77
|
+
__u32 max_discard_sectors;
|
|
78
|
+
__u32 max_write_zeroes_sectors;
|
|
79
|
+
__u16 max_discard_segments;
|
|
80
|
+
__u16 reserved0;
|
|
81
|
+
};
|
|
82
|
+
#endif
|
|
83
|
+
|
|
84
|
+
#ifndef UBLK_F_CMD_IOCTL_ENCODE
|
|
85
|
+
#define UBLK_F_CMD_IOCTL_ENCODE (1ULL << 6)
|
|
86
|
+
#endif
|
|
87
|
+
#ifndef UBLK_F_USER_COPY
|
|
88
|
+
#define UBLK_F_USER_COPY (1ULL << 7)
|
|
89
|
+
#endif
|
|
90
|
+
#ifndef UBLK_MAX_QUEUE_DEPTH
|
|
91
|
+
#define UBLK_MAX_QUEUE_DEPTH 4096
|
|
92
|
+
#endif
|
|
93
|
+
#ifndef UBLK_MAX_NR_QUEUES
|
|
94
|
+
#define UBLK_MAX_NR_QUEUES 4096
|
|
95
|
+
#endif
|
|
96
|
+
#ifndef UBLKSRV_CMD_BUF_OFFSET
|
|
97
|
+
#define UBLKSRV_CMD_BUF_OFFSET 0
|
|
98
|
+
#endif
|
|
99
|
+
#ifndef UBLKSRV_IO_BUF_OFFSET
|
|
100
|
+
#define UBLKSRV_IO_BUF_OFFSET 0x80000000ULL
|
|
101
|
+
#endif
|
|
102
|
+
#ifndef UBLK_CMD_GET_FEATURES
|
|
103
|
+
#define UBLK_CMD_GET_FEATURES 0x13
|
|
104
|
+
#endif
|
|
105
|
+
#ifndef UBLK_IO_BUF_BITS
|
|
106
|
+
#define UBLK_IO_BUF_BITS 25
|
|
107
|
+
#endif
|
|
108
|
+
#ifndef UBLK_TAG_OFF
|
|
109
|
+
#define UBLK_TAG_OFF UBLK_IO_BUF_BITS
|
|
110
|
+
#endif
|
|
111
|
+
#ifndef UBLK_QID_OFF
|
|
112
|
+
#define UBLK_QID_OFF (UBLK_TAG_OFF + 16)
|
|
113
|
+
#endif
|
|
114
|
+
|
|
115
|
+
struct ublk_rb_ctrl_cmd {
|
|
116
|
+
__u32 dev_id;
|
|
117
|
+
__u16 queue_id;
|
|
118
|
+
__u16 len;
|
|
119
|
+
__u64 addr;
|
|
120
|
+
__u64 data[1];
|
|
121
|
+
__u16 dev_path_len;
|
|
122
|
+
__u16 pad;
|
|
123
|
+
__u32 reserved;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
struct ublk_rb_dev_info {
|
|
127
|
+
__u16 nr_hw_queues;
|
|
128
|
+
__u16 queue_depth;
|
|
129
|
+
__u16 state;
|
|
130
|
+
__u16 pad0;
|
|
131
|
+
__u32 max_io_buf_bytes;
|
|
132
|
+
__u32 dev_id;
|
|
133
|
+
__s32 ublksrv_pid;
|
|
134
|
+
__u32 pad1;
|
|
135
|
+
__u64 flags;
|
|
136
|
+
__u64 ublksrv_flags;
|
|
137
|
+
__u32 owner_uid;
|
|
138
|
+
__u32 owner_gid;
|
|
139
|
+
__u64 reserved1;
|
|
140
|
+
__u64 reserved2;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
struct ublk_rb_params {
|
|
144
|
+
__u32 len;
|
|
145
|
+
__u32 types;
|
|
146
|
+
struct ublk_param_basic basic;
|
|
147
|
+
struct ublk_param_discard discard;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
#define UBLK_RB_U_READ_CMD(op) _IOR('u', (op), struct ublk_rb_ctrl_cmd)
|
|
151
|
+
#define UBLK_RB_U_RDWR_CMD(op) _IOWR('u', (op), struct ublk_rb_ctrl_cmd)
|
|
152
|
+
#define UBLK_RB_U_IO_CMD(op) _IOWR('u', (op), struct ublksrv_io_cmd)
|
|
153
|
+
|
|
154
|
+
_Static_assert(sizeof(struct ublk_rb_ctrl_cmd) == 32, "unexpected ublk control command layout");
|
|
155
|
+
_Static_assert(sizeof(struct ublk_rb_dev_info) == 64, "unexpected ublk device info layout");
|
|
156
|
+
_Static_assert(sizeof(struct ublk_rb_params) == 64, "unexpected ublk parameter layout");
|
|
157
|
+
_Static_assert(sizeof(struct ublksrv_io_desc) == 24, "unexpected ublk I/O descriptor layout");
|
|
158
|
+
_Static_assert(sizeof(struct ublksrv_io_cmd) == 16, "unexpected ublk I/O command layout");
|
|
159
|
+
|
|
160
|
+
#ifdef UBLK_U_CMD_GET_FEATURES
|
|
161
|
+
_Static_assert(UBLK_RB_U_READ_CMD(UBLK_CMD_GET_FEATURES) == UBLK_U_CMD_GET_FEATURES,
|
|
162
|
+
"GET_FEATURES command encoding mismatch");
|
|
163
|
+
#endif
|
|
164
|
+
#ifdef UBLK_U_CMD_GET_DEV_INFO
|
|
165
|
+
_Static_assert(UBLK_RB_U_READ_CMD(UBLK_CMD_GET_DEV_INFO) == UBLK_U_CMD_GET_DEV_INFO,
|
|
166
|
+
"GET_DEV_INFO command encoding mismatch");
|
|
167
|
+
#endif
|
|
168
|
+
#ifdef UBLK_U_CMD_ADD_DEV
|
|
169
|
+
_Static_assert(UBLK_RB_U_RDWR_CMD(UBLK_CMD_ADD_DEV) == UBLK_U_CMD_ADD_DEV,
|
|
170
|
+
"ADD_DEV command encoding mismatch");
|
|
171
|
+
#endif
|
|
172
|
+
|
|
173
|
+
#ifdef UBLK_HAVE_UAPI_HEADER
|
|
174
|
+
_Static_assert(sizeof(struct ublk_rb_ctrl_cmd) == sizeof(struct ublksrv_ctrl_cmd),
|
|
175
|
+
"control command size differs from Linux UAPI");
|
|
176
|
+
_Static_assert(offsetof(struct ublk_rb_ctrl_cmd, addr) == offsetof(struct ublksrv_ctrl_cmd, addr),
|
|
177
|
+
"control command address offset differs from Linux UAPI");
|
|
178
|
+
_Static_assert(sizeof(struct ublk_rb_dev_info) == sizeof(struct ublksrv_ctrl_dev_info),
|
|
179
|
+
"device info size differs from Linux UAPI");
|
|
180
|
+
_Static_assert(offsetof(struct ublk_rb_dev_info, flags) == offsetof(struct ublksrv_ctrl_dev_info, flags),
|
|
181
|
+
"device info flags offset differs from Linux UAPI");
|
|
182
|
+
_Static_assert(offsetof(struct ublk_rb_params, basic) == offsetof(struct ublk_params, basic),
|
|
183
|
+
"basic parameter offset differs from Linux UAPI");
|
|
184
|
+
_Static_assert(offsetof(struct ublk_rb_params, discard) == offsetof(struct ublk_params, discard),
|
|
185
|
+
"discard parameter offset differs from Linux UAPI");
|
|
186
|
+
#endif
|
|
187
|
+
|
|
188
|
+
#endif
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* Generated by rake gen:constants. */
|
|
2
|
+
#include "internal.h"
|
|
3
|
+
|
|
4
|
+
#define UBLK_CONST(name) rb_define_const(ublk_native_module, #name, ULL2NUM(name))
|
|
5
|
+
|
|
6
|
+
void ublk_init_constants(void)
|
|
7
|
+
{
|
|
8
|
+
UBLK_CONST(UBLK_F_USER_COPY);
|
|
9
|
+
UBLK_CONST(UBLK_F_CMD_IOCTL_ENCODE);
|
|
10
|
+
UBLK_CONST(UBLK_F_USER_RECOVERY);
|
|
11
|
+
UBLK_CONST(UBLK_MAX_QUEUE_DEPTH);
|
|
12
|
+
UBLK_CONST(UBLKSRV_IO_BUF_OFFSET);
|
|
13
|
+
UBLK_CONST(UBLK_IO_OP_READ);
|
|
14
|
+
UBLK_CONST(UBLK_IO_OP_WRITE);
|
|
15
|
+
UBLK_CONST(UBLK_IO_OP_FLUSH);
|
|
16
|
+
UBLK_CONST(UBLK_IO_OP_DISCARD);
|
|
17
|
+
UBLK_CONST(UBLK_IO_OP_WRITE_ZEROES);
|
|
18
|
+
}
|