Skip to content

Commit b026e14

Browse files
committed
Add ublk-based block device frontend
The existing TCMU frontend requires a single resident daemon for all devices on a host: netlink only notifies the registered handler process, so one crash takes down every overlaybd disk, and the code carries several netlink workarounds as the price of that architecture. Introduce overlaybd-ublk, an alternative frontend based on the kernel's io_uring-backed ublk driver, with no SCSI overhead and no single point of failure: one process serves exactly one device, so device create/delete collapses into process start/stop and failures are isolated to a single disk. * add/del/list CLI following ublk ecosystem conventions; add returns only after the device is usable and prints the device path * per-queue event loop driven by photon: ring fd polled via epoll, CQEs reaped with the non-blocking libublksrv API, IO served by coroutines calling ImageFile directly * IO mapping mirrors the TCMU frontend (READ/WRITE/FLUSH/DISCARD); writable devices honestly advertise a volatile cache, read-only devices do not * works on kernels with partial io_uring feature sets: queue setup retries without IORING_SETUP_COOP_TASKRUN, device deletion prefers DEL_DEV_ASYNC and falls back to a bounded sync del * libublksrv v1.7 and liburing 2.8 are fetched at configure time and statically linked (dual MIT/LGPL, used under MIT); building needs autotools, opt out with -DBUILD_UBLK_FRONTEND=off * verified end-to-end on ublk-capable kernels: startup sequence, idle wakeup, full-queue fio pressure with crc32c verify, FLUSH path, add/del lifecycle, and read-only semantics; 13 unit tests cover the IO mapping and CLI layers Signed-off-by: haolianglh <haolianglh@sina.com>
1 parent e5974c1 commit b026e14

16 files changed

Lines changed: 1372 additions & 2 deletions

.github/workflows/release/build.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,13 @@ elif [[ ${OS} =~ "centos" ]]; then
7373
fi
7474

7575
yum install -y epel-release libaio-devel libcurl-devel openssl-devel libnl3-devel e2fsprogs-devel
76-
yum install -y rpm-build make git wget sudo autoconf automake libtool
76+
# pkgconfig: ublksrv's configure uses PKG_CHECK_MODULES (needs pkg.m4 at autoreconf time)
77+
yum install -y rpm-build make git wget sudo autoconf automake libtool pkgconfig
7778
yum install --skip-broken -y libzstd-static gcc gcc-c++ binutils libzstd-devel
7879
elif [[ ${OS} =~ "mariner" ]]; then
7980
yum install -y libaio-devel libcurl-devel openssl-devel libnl3-devel e2fsprogs-devel glibc-devel libzstd-devel binutils ca-certificates-microsoft build-essential
80-
yum install -y rpm-build make git wget sudo tar gcc gcc-c++ autoconf automake libtool
81+
# pkg-config: ublksrv's configure uses PKG_CHECK_MODULES (needs pkg.m4 at autoreconf time)
82+
yum install -y rpm-build make git wget sudo tar gcc gcc-c++ autoconf automake libtool pkg-config
8183

8284
DISTRO=${OS/:/.}
8385
PACKAGE_RELEASE="-DPACKAGE_RELEASE=${RELEASE_NO}.${DISTRO}"

CMake/Findliburing.cmake

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# liburing (hard dependency of ublksrv).
2+
# Distro liburing packages are commonly missing or too old (>= 2.2 required)
3+
# and behavior differs across versions, so like every other dependency of
4+
# this project it is always fetched at a pinned version and statically
5+
# linked -- never taken from the system.
6+
# FetchContent only downloads; the build uses liburing's own configure/make.
7+
include(FetchContent)
8+
set(FETCHCONTENT_QUIET false)
9+
10+
# CMake 3.30+ deprecates single-argument FetchContent_Populate (CMP0169);
11+
# explicitly keep the old behavior
12+
if(POLICY CMP0169)
13+
cmake_policy(SET CMP0169 OLD)
14+
endif()
15+
16+
FetchContent_Declare(
17+
liburing
18+
GIT_REPOSITORY https://github.com/axboe/liburing.git
19+
GIT_TAG liburing-2.8
20+
)
21+
22+
# download only, no add_subdirectory (liburing is a plain Makefile project)
23+
FetchContent_GetProperties(liburing)
24+
if(NOT liburing_POPULATED)
25+
FetchContent_Populate(liburing)
26+
endif()
27+
28+
if(NOT TARGET liburing_build)
29+
# OUTPUT points at the final artifact => incremental build, skipped once present
30+
add_custom_command(
31+
OUTPUT ${liburing_SOURCE_DIR}/src/liburing.a
32+
WORKING_DIRECTORY ${liburing_SOURCE_DIR}
33+
COMMAND ./configure
34+
COMMAND make -C src -j
35+
COMMENT "Building liburing with its own configure/make"
36+
VERBATIM
37+
)
38+
add_custom_target(liburing_build DEPENDS ${liburing_SOURCE_DIR}/src/liburing.a)
39+
endif()
40+
41+
set(LIBURING_LIBRARIES ${liburing_SOURCE_DIR}/src/liburing.a)
42+
set(LIBURING_INCLUDE_DIRS ${liburing_SOURCE_DIR}/src/include)
43+
44+
# Generate an in-build-tree liburing.pc for ublksrv's configure check
45+
# PKG_CHECK_MODULES([LIBURING], [liburing >= 2.2]) (the official
46+
# build_with_liburing_src flow relies on pkg-config metadata)
47+
set(LIBURING_PC_DIR ${CMAKE_BINARY_DIR}/liburing-pkgconfig)
48+
file(WRITE ${LIBURING_PC_DIR}/liburing.pc
49+
"prefix=${liburing_SOURCE_DIR}
50+
libdir=${liburing_SOURCE_DIR}/src
51+
includedir=${liburing_SOURCE_DIR}/src/include
52+
53+
Name: liburing
54+
Version: 2.8
55+
Description: io_uring library
56+
Libs: -L\${libdir} -luring
57+
Cflags: -I\${includedir}
58+
")
59+
60+
if(NOT TARGET liburing_static)
61+
add_library(liburing_static STATIC IMPORTED GLOBAL)
62+
set_target_properties(liburing_static PROPERTIES
63+
IMPORTED_LOCATION ${LIBURING_LIBRARIES}
64+
INTERFACE_INCLUDE_DIRECTORIES ${LIBURING_INCLUDE_DIRS}
65+
)
66+
endif()
67+
add_dependencies(liburing_static liburing_build)
68+
69+
include(FindPackageHandleStandardArgs)
70+
find_package_handle_standard_args(liburing DEFAULT_MSG LIBURING_LIBRARIES LIBURING_INCLUDE_DIRS)
71+
72+
mark_as_advanced(LIBURING_LIBRARIES LIBURING_INCLUDE_DIRS)

CMake/Findublksrv.cmake

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# ublksrv (ublk userspace library; autotools project, no CMake).
2+
# Only the lib/ directory (pure-C libublksrv) is built:
3+
# - avoids the C++20/-fcoroutines requirement of the ublk CLI tool;
4+
# - libnfs/libiscsi/gnutls only serve its bundled targets, all disabled.
5+
# liburing is built from source by Findliburing.cmake and fed to configure
6+
# via pkg-config/CFLAGS/LDFLAGS (the official build_with_liburing_src flow).
7+
# License: lib/ + include/ublksrv.h are dual MIT/LGPL; linked under MIT.
8+
include(FetchContent)
9+
set(FETCHCONTENT_QUIET false)
10+
11+
if(POLICY CMP0169)
12+
cmake_policy(SET CMP0169 OLD)
13+
endif()
14+
15+
# the autotools build needs extra host tools; fail early with a clear message
16+
find_program(AUTORECONF_EXECUTABLE autoreconf)
17+
find_program(LIBTOOLIZE_EXECUTABLE libtoolize)
18+
find_program(AUTOMAKE_EXECUTABLE automake)
19+
if(NOT AUTORECONF_EXECUTABLE OR NOT LIBTOOLIZE_EXECUTABLE OR NOT AUTOMAKE_EXECUTABLE)
20+
message(FATAL_ERROR
21+
"BUILD_UBLK_FRONTEND requires autotools (autoconf/automake/libtool) "
22+
"to build ublksrv. Install them or configure with -DBUILD_UBLK_FRONTEND=off")
23+
endif()
24+
25+
FetchContent_Declare(
26+
ublksrv
27+
GIT_REPOSITORY https://github.com/ublk-org/ublksrv.git
28+
GIT_TAG f6c643952d1cdc7f6460630638fe6b5454ca1c4d # v1.7
29+
)
30+
31+
FetchContent_GetProperties(ublksrv)
32+
if(NOT ublksrv_POPULATED)
33+
FetchContent_Populate(ublksrv)
34+
endif()
35+
36+
if(NOT TARGET libublksrv_build)
37+
add_custom_command(
38+
OUTPUT ${ublksrv_SOURCE_DIR}/lib/.libs/libublksrv.a
39+
WORKING_DIRECTORY ${ublksrv_SOURCE_DIR}
40+
COMMAND autoreconf -i
41+
COMMAND ${CMAKE_COMMAND} -E env PKG_CONFIG_PATH=${LIBURING_PC_DIR}
42+
./configure
43+
--without-libnfs --without-libiscsi --without-gnutls
44+
# configure appends -fcoroutines whenever $CXX matches *g++*
45+
# (GCC 10+ only, and only the ublk tool needs it, lib/ does not);
46+
# use the name c++ to sidestep that match
47+
CXX=c++
48+
"CFLAGS=-I${LIBURING_INCLUDE_DIRS} -O2"
49+
"CXXFLAGS=-I${LIBURING_INCLUDE_DIRS} -O2"
50+
"LDFLAGS=-L${liburing_SOURCE_DIR}/src"
51+
COMMAND make -C lib -j
52+
DEPENDS ${LIBURING_LIBRARIES}
53+
COMMENT "Building libublksrv (lib/ only) with autotools"
54+
VERBATIM
55+
)
56+
add_custom_target(libublksrv_build DEPENDS ${ublksrv_SOURCE_DIR}/lib/.libs/libublksrv.a)
57+
add_dependencies(libublksrv_build liburing_build)
58+
endif()
59+
60+
set(UBLKSRV_LIBRARIES ${ublksrv_SOURCE_DIR}/lib/.libs/libublksrv.a)
61+
set(UBLKSRV_INCLUDE_DIRS ${ublksrv_SOURCE_DIR}/include)
62+
63+
if(NOT TARGET libublksrv_static)
64+
add_library(libublksrv_static STATIC IMPORTED GLOBAL)
65+
set_target_properties(libublksrv_static PROPERTIES
66+
IMPORTED_LOCATION ${UBLKSRV_LIBRARIES}
67+
INTERFACE_INCLUDE_DIRECTORIES ${UBLKSRV_INCLUDE_DIRS}
68+
# libublksrv.a references liburing symbols; propagate via INTERFACE so
69+
# consumers get the correct link order automatically
70+
INTERFACE_LINK_LIBRARIES liburing_static
71+
)
72+
endif()
73+
add_dependencies(libublksrv_static libublksrv_build)
74+
75+
include(FindPackageHandleStandardArgs)
76+
find_package_handle_standard_args(ublksrv DEFAULT_MSG UBLKSRV_LIBRARIES UBLKSRV_INCLUDE_DIRS)
77+
78+
mark_as_advanced(UBLKSRV_LIBRARIES UBLKSRV_INCLUDE_DIRS)

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,17 @@ set(ENABLE_MIMIC_VDSO off)
5353
option(BUILD_CURL_FROM_SOURCE "Compile static libcurl" off)
5454
option(BUILD_STREAM_CONVERTOR "Build the stream convertor" on)
5555
option(ORIGIN_EXT2FS "Use original libext2fs" off)
56+
# Building it requires autotools on the build machine; disable with -DBUILD_UBLK_FRONTEND=off
57+
option(BUILD_UBLK_FRONTEND "Build the ublk frontend (overlaybd-ublk), requires autotools" on)
5658

5759
find_package(photon REQUIRED)
5860
find_package(tcmu REQUIRED)
5961

62+
if(BUILD_UBLK_FRONTEND)
63+
find_package(liburing REQUIRED)
64+
find_package(ublksrv REQUIRED)
65+
endif()
66+
6067
if(BUILD_STREAM_CONVERTOR)
6168
find_package(yaml-cpp)
6269
if (NOT yaml-cpp_FOUND)

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,42 @@ cmake -D ENABLE_QAT=1 ..
119119

120120
For more information go to `overlaybd/src/overlaybd/zfile/README.md`.
121121

122+
If you want to build the ublk frontend (`overlaybd-ublk`), which exposes an
123+
image as `/dev/ublkbN` without going through TCMU/SCSI. It is built by default
124+
(disable with `-D BUILD_UBLK_FRONTEND=off`); building it additionally requires
125+
`autoconf`, `automake` and `libtool` (liburing and libublksrv are fetched and
126+
built from source automatically).
127+
128+
```bash
129+
cmake -D BUILD_UBLK_FRONTEND=on ..
130+
```
131+
132+
Running it requires a kernel with the `ublk_drv` driver (mainline >= 6.0, or a
133+
distro kernel with ublk backported):
134+
135+
```bash
136+
sudo modprobe ublk_drv
137+
sudo overlaybd-ublk add --config /path/to/config.v1.json # prints /dev/ublkbN when ready
138+
sudo overlaybd-ublk list
139+
sudo overlaybd-ublk del -n 0
140+
```
141+
142+
Unlike `overlaybd-tcmu`, one `overlaybd-ublk` process serves exactly one
143+
device; killing the daemon removes the device. Each device can write to its
144+
own log file via `add --log-path ...` (recommended when running multiple
145+
devices; daemons otherwise share the global log file and are distinguished
146+
by a `ublk-<pid>` tag).
147+
148+
Notes:
149+
150+
* Always unmount filesystems on `/dev/ublkbN` before `del` (or before stopping
151+
the daemon): the daemon serves the device's IO, so tearing it down with a
152+
mounted filesystem aborts in-flight journal writes.
153+
* Runtime verified on a 5.10 kernel with ublk backported (Alinux
154+
5.10.134); a full regression on mainline 6.x kernels is still pending.
155+
Known backport-kernel caveats: DISCARD is unavailable there, and `del`
156+
takes ~2s instead of milliseconds.
157+
122158
Finally, setup a systemd service for overlaybd-tcmu backstore.
123159

124160
```bash

src/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ if (NOT ORIGIN_EXT2FS)
7575
endif()
7676

7777
add_subdirectory(tools)
78+
if (BUILD_UBLK_FRONTEND)
79+
add_subdirectory(ublk)
80+
endif ()
7881
if (BUILD_TESTING)
7982
add_subdirectory(test)
8083
endif ()

src/ublk/CMakeLists.txt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# overlaybd-ublk: the ublk block device frontend (one process serves one device)
2+
# Entered only when BUILD_UBLK_FRONTEND=on
3+
4+
add_library(ublk_frontend_lib
5+
io_dispatch.cpp
6+
ublk_device.cpp
7+
cli.cpp
8+
)
9+
target_include_directories(ublk_frontend_lib PUBLIC
10+
${PHOTON_INCLUDE_DIR}
11+
${RAPIDJSON_INCLUDE_DIRS}
12+
)
13+
target_link_libraries(ublk_frontend_lib
14+
photon_static
15+
overlaybd_image_lib
16+
libublksrv_static # IMPORTED target; include dirs and liburing propagate via INTERFACE
17+
)
18+
19+
add_executable(overlaybd-ublk main.cpp)
20+
target_link_libraries(overlaybd-ublk
21+
ublk_frontend_lib
22+
photon_static
23+
overlaybd_image_lib
24+
libublksrv_static
25+
${CURL_LIBRARIES}
26+
${OPENSSL_SSL_LIBRARY}
27+
${OPENSSL_CRYPTO_LIBRARY}
28+
${AIO_LIBRARIES}
29+
)
30+
31+
install(TARGETS overlaybd-ublk DESTINATION /opt/overlaybd/bin)
32+
33+
if (BUILD_TESTING)
34+
add_subdirectory(test)
35+
endif ()

src/ublk/cli.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
Copyright The Overlaybd Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
#include "cli.h"
17+
18+
#include "../tools/CLI11.hpp"
19+
20+
int ublk_parse_cli(int argc, char **argv, UblkCliCmd &cmd) {
21+
CLI::App app{"overlaybd-ublk: expose an overlaybd image as /dev/ublkbN "
22+
"(one process serves one device)"};
23+
app.require_subcommand(1);
24+
25+
auto *add = app.add_subcommand("add", "create a ublk device from an image config");
26+
add->add_option("--config", cmd.opts.image_config_path,
27+
"overlaybd image config path (config.v1.json)")
28+
->required()
29+
->check(CLI::ExistingFile);
30+
add->add_option("-n,--dev-id", cmd.opts.dev_id,
31+
"device id (default -1: allocated by kernel)");
32+
add->add_option("--depth", cmd.opts.queue_depth, "queue depth")
33+
->default_val(128)
34+
->check(CLI::Range(1, 4096));
35+
add->add_option("--service-config", cmd.opts.service_config_path,
36+
"global service config path (default /etc/overlaybd/overlaybd.json)");
37+
add->add_option("--log-path", cmd.opts.log_path,
38+
"per-device log file (default: shared log from service config; "
39+
"recommended when running multiple devices)");
40+
add->add_flag("--foreground", cmd.foreground, "run in foreground (no daemonize)");
41+
42+
auto *del = app.add_subcommand("del", "stop the daemon serving /dev/ublkbN");
43+
del->add_option("-n,--dev-id", cmd.del_dev_id, "device id")->required();
44+
45+
app.add_subcommand("list", "list overlaybd-ublk devices on this host");
46+
47+
try {
48+
app.parse(argc, argv);
49+
} catch (const CLI::ParseError &e) {
50+
// help returns 0, errors return non-zero; cmd.kind stays NONE in
51+
// both cases so the caller just exits with this code
52+
cmd.kind = UblkCliCmd::Kind::NONE;
53+
return app.exit(e);
54+
}
55+
56+
if (app.got_subcommand("add"))
57+
cmd.kind = UblkCliCmd::Kind::ADD;
58+
else if (app.got_subcommand("del"))
59+
cmd.kind = UblkCliCmd::Kind::DEL;
60+
else
61+
cmd.kind = UblkCliCmd::Kind::LIST;
62+
return 0;
63+
}

src/ublk/cli.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright The Overlaybd Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
#pragma once
17+
18+
#include "ublk_device.h"
19+
20+
// Parsed CLI command, kept apart from execution for unit testing.
21+
struct UblkCliCmd {
22+
enum class Kind { NONE, ADD, DEL, LIST };
23+
Kind kind = Kind::NONE;
24+
25+
UblkDeviceOpts opts; // ADD
26+
bool foreground = false;
27+
28+
int del_dev_id = -1; // DEL
29+
};
30+
31+
// Parse argv into cmd. On success returns 0 with cmd.kind set; on
32+
// error/help prints the message (CLI11 behavior), leaves cmd.kind as NONE
33+
// and returns the intended process exit code (0 for help).
34+
int ublk_parse_cli(int argc, char **argv, UblkCliCmd &cmd);

0 commit comments

Comments
 (0)