Skip to content
33 changes: 33 additions & 0 deletions bsd/porting/netport1.cc
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,36 @@ int openzfs_cv_timedwait(kcondvar_t *cv, mutex_t *mutex, clock_t abstime)
auto ret = cv->wait(mutex, std::chrono::nanoseconds(ticks2ns(delta)));
return ret == ETIMEDOUT ? -1 : 0;
}

// OpenZFS cv_timedwait_hires: a NANOSECOND-precision timed wait. The ZIL
// commit path (zil_commit_waiter_timeout) sizes its commit-batch window as a
// small fraction (~10%) of the last log-write latency -- typically hundreds of
// microseconds. Routing that through a tick-granular wait (hz=1000 -> 1ms
// ticks) rounds a sub-millisecond window down to zero, so concurrent fsyncs
// never coalesce into one log write + one device cache flush; every commit
// pays its own synchronous flush. Wait the exact nanosecond delay instead.
// `deadline_ns` is an absolute gethrtime() (wall-clock, CLOCK_REALTIME) value when
// CALLOUT_FLAG_ABSOLUTE is set, otherwise a relative nanosecond delay; either
// way we wait the true remaining nanoseconds so the ZIL batch window is
// honored and commits coalesce.
OSV_LIBSOLARIS_API
int openzfs_cv_timedwait_hires(kcondvar_t *cv, mutex_t *mutex,
long long deadline_ns, int absolute)
{
long long delta_ns = deadline_ns;
if (absolute) {
// gethrtime() is clock_gettime(CLOCK_UPTIME), and CLOCK_UPTIME is
// #defined to CLOCK_REALTIME, so an absolute deadline is a wall-clock
// nanosecond value. Subtract wall-clock now (not uptime, which is
// ~1e11 ns at boot and would leave a ~1.7e18 ns / >50 year delay,
// arming the ZIL commit-batch timeout effectively forever).
u64 now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>
(osv::clock::wall::now().time_since_epoch()).count();
delta_ns = deadline_ns - (long long)now_ns;
}
if (delta_ns <= 0) {
return -1;
}
auto ret = cv->wait(mutex, std::chrono::nanoseconds(delta_ns));
return ret == ETIMEDOUT ? -1 : 0;
}
6 changes: 6 additions & 0 deletions bsd/sys/cddl/compat/opensolaris/sys/kcondvar.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ typedef enum {
int cv_timedwait(kcondvar_t *cv, mutex_t *mutex, clock_t tmo);
// OpenZFS variant: absolute deadline (see bsd/porting/netport1.cc).
int openzfs_cv_timedwait(kcondvar_t *cv, mutex_t *mutex, clock_t abstime);
// OpenZFS hires variant: nanosecond-precision timed wait. deadline_ns is an
// absolute gethrtime() (wall-clock, CLOCK_REALTIME) deadline when absolute!=0, else a relative
// nanosecond delay. Used by cv_timedwait_hires so the ZIL commit-batch window
// (sub-millisecond) is honored instead of being rounded to zero by ticks.
int openzfs_cv_timedwait_hires(kcondvar_t *cv, mutex_t *mutex,
long long deadline_ns, int absolute);

#ifdef __cplusplus
}
Expand Down
55 changes: 46 additions & 9 deletions loader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
#include "libc/network/__dns.hh"
#include <processor.hh>
#include <dlfcn.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <osv/string_utils.hh>

using namespace osv;
Expand Down Expand Up @@ -480,8 +482,15 @@ static void stop_all_remaining_app_threads()

static int load_fs_library(const char* fs_library_path, std::function<int()> on_load_fun = nullptr)
{
// Load and initialize filesystem driver
if (dlopen(fs_library_path, RTLD_LAZY)) {
// Load and initialize filesystem driver. RTLD_GLOBAL is load-bearing for
// the ZFS case: the userspace tools (/zpool.so, /zfs.so) are linked only
// against libzfs.so + libc and resolve their nvpair/nvlist/fnvlist symbols
// (~60 of them) against libsolaris.so at runtime. Loading libsolaris with
// RTLD_LOCAL leaves those symbols invisible to a later dlopen of the tools,
// so zpool.so loads with dozens of "ignoring missing symbol nvpair_*" and
// then cannot talk to the kernel ("/dev/zfs not found"). RTLD_GLOBAL puts
// libsolaris's exports in the global scope so the tools resolve correctly.
if (dlopen(fs_library_path, RTLD_LAZY | RTLD_GLOBAL)) {
if (on_load_fun) {
return on_load_fun();
} else {
Expand Down Expand Up @@ -552,6 +561,40 @@ void* do_main_thread(void *_main_args)
}
boot_time.event("drivers loaded");

// Preload the ZFS library WITHOUT mounting a ZFS root, BEFORE the root-mount
// block. Used by the ZFS builder, and by any image whose root filesystem
// is NOT zfs (e.g. fs=ramfs) but which still wants to create/import a ZFS
// *data* pool at runtime. This must run before opt_mount's unmount/pivot
// dance: after that, dlopen of /usr/lib/fs/libsolaris.so from the bootfs
// ramfs fails to open (the path is no longer resolvable), which is why the
// fs=zfs path (which dlopens libsolaris inside the mount block, before the
// pivot) works while a later preload did not. libsolaris.so alone is not
// enough: the in-kernel ZFS control device /dev/zfs must also exist, else
// zpool/zfs commands fail with "/dev/zfs not found". So also run
// zfsdev_init() (as load_zfs_library_and_mount_zfs_root does for fs=zfs)
// and create an empty /etc/mnttab (as the in-tree zfs bench harness does)
// so a ramfs-root image can bring up a ZFS data pool on a local disk.
//
// The ZFS builder boots with --noinit and initializes the control device
// itself from its own bootfs tool, so only bring the device up here when
// the image runs its normal init.
if (opt_preload_zfs_library) {
bool init_zfsdev = opt_init;
if (load_fs_library(libsolaris_path, [init_zfsdev]() {
if (init_zfsdev) {
zfsdev::zfsdev_init();
mkdir("/etc", 0755);
int fd = creat("/etc/mnttab", 0644);
if (fd >= 0)
close(fd);
}
return 0;
})) {
fprintf(stderr, "Failed to preload ZFS library. Powering off.\n");
osv::poweroff();
}
}

if (opt_mount) {
unmount_devfs();

Expand Down Expand Up @@ -605,13 +648,7 @@ void* do_main_thread(void *_main_args)
}
}

//This option is only used by ZFS builder
if (opt_preload_zfs_library) {
if (load_fs_library(libsolaris_path)) {
fprintf(stderr, "Failed to preload ZFS library. Powering off.\n");
osv::poweroff();
}
}
// (ZFS preload moved earlier, before the root-mount block.)

#if CONF_networking_stack
bool has_if = false;
Expand Down
18 changes: 15 additions & 3 deletions modules/open_zfs/module.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from osv.modules import api
import os

# The `open_zfs` module PROVIDES the `zfs` capability for the vendored OpenZFS
# 2.4.x implementation (conf_zfs=openzfs), analogous to how
Expand All @@ -7,7 +8,18 @@
# normal parent-tracked source in modules/open_zfs/osv, and a single small patch
# for the ~15 edited upstream files in modules/open_zfs/patches (applied at build
# time). The kernel objects are linked into libsolaris.so by the top-level Makefile, which for
# conf_zfs=openzfs includes modules/open_zfs/open_zfs_sources.mk. This module
# carries no extra manifest of its own; the `zfs` placeholder module selects it
# via conf_zfs.
# conf_zfs=openzfs includes modules/open_zfs/open_zfs_sources.mk.
#
# libsolaris.so must be listed by the module that actually provides ZFS, not by
# the `zfs` placeholder: the placeholder is replaced by its required provider
# during module resolution, so a manifest carried only by the placeholder is
# dropped and libsolaris.so never lands in usr.manifest. That silently omits
# it from a bootfs-populated (fs=ramfs) image, where --preload-zfs-library then
# fails to dlopen /usr/lib/fs/libsolaris.so. Generate the manifest entry here
# (written at import time, matching zfs-tools) so the provider carries it.
_manifest = os.path.join(os.path.dirname(__file__), 'usr.manifest')
with open(_manifest, 'w') as f:
f.write('[manifest]\n')
f.write('/usr/lib/fs/libsolaris.so: libsolaris.so\n')

provides = ['zfs']
23 changes: 13 additions & 10 deletions modules/open_zfs/osv/include/os/osv/spl/sys/condvar.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,19 @@ static inline int
cv_timedwait_hires(kcondvar_t *cvp, mutex_t *mp, long long tim,
long long res __attribute__((unused)), int flag)
{
clock_t ticks;

if (flag == 0) {
/* Relative time: add current hrtime */
tim += gethrtime();
}

/* Convert absolute hrtime (nanoseconds) to tick deadline */
ticks = (clock_t)(tim / (1000000000LL / hz));
return (cv_timedwait(cvp, mp, ticks));
/*
* Nanosecond-precise wait. Tick granularity (1ms at hz=1000) would
* round the sub-millisecond ZIL commit-batch window
* (zil_commit_waiter_timeout sizes it as ~10% of the last log-write
* latency) down to zero, so concurrent fsyncs never coalesce into one
* log write plus one device cache flush and every commit pays its own
* synchronous flush. `tim` is an absolute gethrtime() (wall-clock, CLOCK_REALTIME)
* deadline when the flag (ABSOLUTE) is nonzero, else a relative
* nanosecond delay.
*/
extern int openzfs_cv_timedwait_hires(kcondvar_t *, mutex_t *,
long long, int);
return (openzfs_cv_timedwait_hires(cvp, mp, tim, flag != 0));
}

#define cv_timedwait_sig_hires cv_timedwait_hires
Expand Down