mirror of
https://github.com/rdmsr/zag.git
synced 2026-11-01 23:58:21 +00:00
mm/zone: add SMR zones
This commit is contained in:
+193
-42
@@ -5,7 +5,7 @@
|
|||||||
//! the Slab Allocator to Many CPUs and Arbitrary Resources", both from Solaris.
|
//! the Slab Allocator to Many CPUs and Arbitrary Resources", both from Solaris.
|
||||||
//!
|
//!
|
||||||
//! ## Structure
|
//! ## Structure
|
||||||
//! ----------------
|
//! ------------
|
||||||
//! The allocator is split in three layers:
|
//! The allocator is split in three layers:
|
||||||
//! 1. Per-CPU layer
|
//! 1. Per-CPU layer
|
||||||
//! 2. Zone depot layer
|
//! 2. Zone depot layer
|
||||||
@@ -18,10 +18,9 @@
|
|||||||
//! ## Per-CPU layer
|
//! ## Per-CPU layer
|
||||||
//! ----------------
|
//! ----------------
|
||||||
//! Each CPU has an `alloc` and `free` magazine, where objects are allocated
|
//! Each CPU has an `alloc` and `free` magazine, where objects are allocated
|
||||||
//! from and freed to, respectively. This separation avoids hysteresis and might
|
//! from and freed to, respectively. This separation avoids hysteresis and is useful
|
||||||
//! prove useful in the future for safe memory reclamation mechanisms (see
|
//! for safe memory reclamation (see below).
|
||||||
//! FreeBSD GUS or XNU SMR). When one side runs dry/full, the magazines may be
|
//! When one side runs dry/full, the magazines may be swapped, if SMR is disabled.
|
||||||
//! swapped.
|
|
||||||
//! Each CPU also has its own depot, from which it allocates and frees its
|
//! Each CPU also has its own depot, from which it allocates and frees its
|
||||||
//! magazines to first. If there is no magazine in the local depot, the magazine
|
//! magazines to first. If there is no magazine in the local depot, the magazine
|
||||||
//! will be provided from the zone depot layer. We keep track of the size of the
|
//! will be provided from the zone depot layer. We keep track of the size of the
|
||||||
@@ -77,11 +76,35 @@
|
|||||||
//! that the poison data is still intact inside an object, detecting
|
//! that the poison data is still intact inside an object, detecting
|
||||||
//! use-after-free bugs.
|
//! use-after-free bugs.
|
||||||
//!
|
//!
|
||||||
|
//! ## SMR zones
|
||||||
|
//! ------------
|
||||||
|
//! A zone may be tied to a SMR domain, in which case freed objects may not
|
||||||
|
//! be reused until a given sequence has been observed by all CPUs. The magazine
|
||||||
|
//! layer is used to amortize and batch this operation; instead of tracking a
|
||||||
|
//! per-object sequence, whole magazines are stamped.
|
||||||
|
//!
|
||||||
|
//! When the free magazine becomes full, it is stamped with a *deferred* advance
|
||||||
|
//! of the domain's write sequence, this advance is only committed once the
|
||||||
|
//! magazine actually reaches the zone depot. This is meant to pace commits with
|
||||||
|
//! regards to the per-CPU depot size, where a larger size will lead to less
|
||||||
|
//! commits. When alloc needs a full magazine, it first polls the sequence of
|
||||||
|
//! the head of the depot magazine list (since SMR zones are forced
|
||||||
|
//! FIFO and stamps are taken in monotonic order, the head always carries the
|
||||||
|
//! minimum sequence). If the local head has not expired, the local full magazines
|
||||||
|
//! are moved and appended to the zone depot to age, and their sequences
|
||||||
|
//! are committed, while expired magazines from the zone depot are pulled back in
|
||||||
|
//! to satisfy the request.
|
||||||
|
//!
|
||||||
|
//! All this machinery is to avoid expensive scans during SMR polling,
|
||||||
|
//! growing the per-CPU depots widens the window between a stamp and its poll
|
||||||
|
//! until the fast path (which is a single load) is all that runs.
|
||||||
|
//!
|
||||||
//! ## Extra notes
|
//! ## Extra notes
|
||||||
//! ---------------------
|
//! --------------
|
||||||
//! With poisoning enabled, constructors run when an object is allocated and
|
//! With poisoning enabled, constructors run when an object is allocated and
|
||||||
//! destructors run when it is freed. Without poisoning, constructors run once
|
//! destructors run when it is freed. Without poisoning, constructors run when
|
||||||
//! when the slab is created and destructors run when the slab is destroyed.
|
//! an object leaves the slab layer and destructors run when it returns to it,
|
||||||
|
//! slabs hold raw objects while magazines hold constructed ones.
|
||||||
|
|
||||||
const rtl = @import("rtl");
|
const rtl = @import("rtl");
|
||||||
const r = @import("root");
|
const r = @import("root");
|
||||||
@@ -153,6 +176,9 @@ var zone_list_lock: ke.Mutex = .init();
|
|||||||
var magazine_zone: Zone = undefined;
|
var magazine_zone: Zone = undefined;
|
||||||
var cpu_zone: Zone = undefined;
|
var cpu_zone: Zone = undefined;
|
||||||
|
|
||||||
|
var smr_zone: TypedZone(ke.smr.Domain) = undefined;
|
||||||
|
var smr_cpu_zone: Zone = undefined;
|
||||||
|
|
||||||
const alloc_poison: u32 = 0xBADDC0DE;
|
const alloc_poison: u32 = 0xBADDC0DE;
|
||||||
const free_poison: u32 = 0xDEADBEEF;
|
const free_poison: u32 = 0xDEADBEEF;
|
||||||
const is_poison_enabled = config.slab_poison;
|
const is_poison_enabled = config.slab_poison;
|
||||||
@@ -282,6 +308,8 @@ const Slab = struct {
|
|||||||
const Magazine = struct {
|
const Magazine = struct {
|
||||||
/// Next magazine in the list.
|
/// Next magazine in the list.
|
||||||
next: ?*Magazine,
|
next: ?*Magazine,
|
||||||
|
/// Stamped sequence if the zone is SMR.
|
||||||
|
seq: ke.smr.Sequence,
|
||||||
/// Rounds follow in memory after the struct.
|
/// Rounds follow in memory after the struct.
|
||||||
pub fn rounds_ptr(self: *Magazine) [*]*anyopaque {
|
pub fn rounds_ptr(self: *Magazine) [*]*anyopaque {
|
||||||
return @ptrFromInt(@intFromPtr(self) + @sizeOf(Magazine));
|
return @ptrFromInt(@intFromPtr(self) + @sizeOf(Magazine));
|
||||||
@@ -329,7 +357,9 @@ const Depot = struct {
|
|||||||
|
|
||||||
list.tail = mag;
|
list.tail = mag;
|
||||||
} else {
|
} else {
|
||||||
|
mag.next = list.head;
|
||||||
list.head = mag;
|
list.head = mag;
|
||||||
|
if (list.tail == null) list.tail = mag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,8 +463,6 @@ pub const Zone = struct {
|
|||||||
slab_size: usize,
|
slab_size: usize,
|
||||||
/// Size of a chunk.
|
/// Size of a chunk.
|
||||||
chunk_size: usize,
|
chunk_size: usize,
|
||||||
/// Offset for buf-to-bufctl conversion.
|
|
||||||
offset: usize,
|
|
||||||
/// Max color for slab coloring.
|
/// Max color for slab coloring.
|
||||||
max_color: u16,
|
max_color: u16,
|
||||||
/// Current color for slab coloring.
|
/// Current color for slab coloring.
|
||||||
@@ -468,6 +496,8 @@ pub const Zone = struct {
|
|||||||
|
|
||||||
reuse_policy: ReusePolicy,
|
reuse_policy: ReusePolicy,
|
||||||
|
|
||||||
|
smr: ?*ke.smr.Domain,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
const InitOptions = struct {
|
const InitOptions = struct {
|
||||||
@@ -476,6 +506,7 @@ pub const Zone = struct {
|
|||||||
dtor: ?*const fn (*anyopaque) void = null,
|
dtor: ?*const fn (*anyopaque) void = null,
|
||||||
magazines: bool = true,
|
magazines: bool = true,
|
||||||
reuse_policy: ReusePolicy = .FIFO,
|
reuse_policy: ReusePolicy = .FIFO,
|
||||||
|
smr: ?*ke.smr.Domain = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Initialize a zone.
|
/// Initialize a zone.
|
||||||
@@ -512,7 +543,11 @@ pub const Zone = struct {
|
|||||||
self.cpu_depot_size = 0;
|
self.cpu_depot_size = 0;
|
||||||
self.cpu_depot_limit = max_local_memory.load() / (self.chunk_size * magazine_size.load());
|
self.cpu_depot_limit = max_local_memory.load() / (self.chunk_size * magazine_size.load());
|
||||||
self.trim_depot = false;
|
self.trim_depot = false;
|
||||||
self.reuse_policy = options.reuse_policy;
|
|
||||||
|
// SMR zones have to be FIFO so that the minimum sequence can be
|
||||||
|
// obtained by checking the list head.
|
||||||
|
self.reuse_policy = if (options.smr != null) .FIFO else options.reuse_policy;
|
||||||
|
self.smr = options.smr;
|
||||||
|
|
||||||
zone_list_lock.acquire();
|
zone_list_lock.acquire();
|
||||||
|
|
||||||
@@ -572,7 +607,7 @@ pub const Zone = struct {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cpu.free_rounds > 0) {
|
if (cpu.free_rounds > 0 and self.smr == null) {
|
||||||
std.debug.assert(cpu.free != null);
|
std.debug.assert(cpu.free != null);
|
||||||
|
|
||||||
// Alloc magazine is empty. If the free magazine has items, swap them.
|
// Alloc magazine is empty. If the free magazine has items, swap them.
|
||||||
@@ -584,12 +619,15 @@ pub const Zone = struct {
|
|||||||
|
|
||||||
// Both magazines are empty.
|
// Both magazines are empty.
|
||||||
// Try to get a full magazine from the CPU-local depot.
|
// Try to get a full magazine from the CPU-local depot.
|
||||||
if (Depot.alloc(&cpu.depot.full_mags)) |full_mag| {
|
if (self.full_mags_ready(&cpu.depot.full_mags)) {
|
||||||
|
const full_mag = Depot.alloc(&cpu.depot.full_mags).?;
|
||||||
|
|
||||||
// Discard our empty alloc magazine to the depot.
|
// Discard our empty alloc magazine to the depot.
|
||||||
if (cpu.alloc) |empty_mag| {
|
if (cpu.alloc) |empty_mag| {
|
||||||
Depot.free(empty_mag, &cpu.depot.empty_mags, self.reuse_policy);
|
Depot.free(empty_mag, &cpu.depot.empty_mags, self.reuse_policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.magazine_reuse(full_mag);
|
||||||
cpu.alloc = full_mag;
|
cpu.alloc = full_mag;
|
||||||
cpu.alloc_rounds = magazine_size.load();
|
cpu.alloc_rounds = magazine_size.load();
|
||||||
continue;
|
continue;
|
||||||
@@ -603,12 +641,15 @@ pub const Zone = struct {
|
|||||||
self.alloc_depot_rebalance(n, cpu);
|
self.alloc_depot_rebalance(n, cpu);
|
||||||
|
|
||||||
// Try getting a full magazine from the CPU-local depot again.
|
// Try getting a full magazine from the CPU-local depot again.
|
||||||
if (Depot.alloc(&cpu.depot.full_mags)) |full_mag| {
|
if (self.full_mags_ready(&cpu.depot.full_mags)) {
|
||||||
|
const full_mag = Depot.alloc(&cpu.depot.full_mags).?;
|
||||||
|
|
||||||
// Discard our empty alloc magazine to the depot.
|
// Discard our empty alloc magazine to the depot.
|
||||||
if (cpu.alloc) |empty_mag| {
|
if (cpu.alloc) |empty_mag| {
|
||||||
Depot.free(empty_mag, &cpu.depot.empty_mags, self.reuse_policy);
|
Depot.free(empty_mag, &cpu.depot.empty_mags, self.reuse_policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.magazine_reuse(full_mag);
|
||||||
cpu.alloc = full_mag;
|
cpu.alloc = full_mag;
|
||||||
cpu.alloc_rounds = magazine_size.load();
|
cpu.alloc_rounds = magazine_size.load();
|
||||||
continue;
|
continue;
|
||||||
@@ -659,10 +700,10 @@ pub const Zone = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fill_with_poison(buf, self.obj_size, alloc_poison);
|
fill_with_poison(buf, self.obj_size, alloc_poison);
|
||||||
|
}
|
||||||
|
|
||||||
if (self.ctor) |ctor| {
|
if (self.ctor) |ctor| {
|
||||||
ctor(buf);
|
ctor(buf);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.lock.release();
|
self.lock.release();
|
||||||
@@ -681,7 +722,9 @@ pub const Zone = struct {
|
|||||||
const freed = while (true) {
|
const freed = while (true) {
|
||||||
if (cpu.free != null and cpu.free_rounds < magazine_size.load()) {
|
if (cpu.free != null and cpu.free_rounds < magazine_size.load()) {
|
||||||
// Fast path: push directly to free magazine.
|
// Fast path: push directly to free magazine.
|
||||||
if (is_poison_enabled) {
|
// For SMR zones the dtor and poisoning are deferred until
|
||||||
|
// the magazine is reused, readers may still hold the object.
|
||||||
|
if (is_poison_enabled and self.smr == null) {
|
||||||
if (self.dtor) |dtor| {
|
if (self.dtor) |dtor| {
|
||||||
dtor(obj);
|
dtor(obj);
|
||||||
}
|
}
|
||||||
@@ -691,10 +734,16 @@ pub const Zone = struct {
|
|||||||
|
|
||||||
cpu.free.?.rounds_ptr()[cpu.free_rounds] = obj;
|
cpu.free.?.rounds_ptr()[cpu.free_rounds] = obj;
|
||||||
cpu.free_rounds += 1;
|
cpu.free_rounds += 1;
|
||||||
|
|
||||||
|
if (self.smr != null and cpu.free_rounds == magazine_size.load()) {
|
||||||
|
// Stamp the now-full magazine.
|
||||||
|
cpu.free.?.seq = ke.smr.deferred_advance(self.smr.?);
|
||||||
|
}
|
||||||
|
|
||||||
break true;
|
break true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cpu.alloc_rounds == 0 and cpu.alloc != null) {
|
if (cpu.alloc_rounds == 0 and cpu.alloc != null and self.smr == null) {
|
||||||
// Free magazine is full. If the alloc magazine is completely empty, swap them.
|
// Free magazine is full. If the alloc magazine is completely empty, swap them.
|
||||||
// The empty alloc magazine becomes our new free magazine.
|
// The empty alloc magazine becomes our new free magazine.
|
||||||
std.mem.swap(?*Magazine, &cpu.alloc, &cpu.free);
|
std.mem.swap(?*Magazine, &cpu.alloc, &cpu.free);
|
||||||
@@ -745,6 +794,8 @@ pub const Zone = struct {
|
|||||||
cpu.lock.acquire_no_ipl();
|
cpu.lock.acquire_no_ipl();
|
||||||
|
|
||||||
if (new_mag) |m| {
|
if (new_mag) |m| {
|
||||||
|
m.seq = ke.smr.seq_invalid;
|
||||||
|
|
||||||
// Got one, try again.
|
// Got one, try again.
|
||||||
Depot.free(m, &cpu.depot.empty_mags, self.reuse_policy);
|
Depot.free(m, &cpu.depot.empty_mags, self.reuse_policy);
|
||||||
continue;
|
continue;
|
||||||
@@ -758,16 +809,24 @@ pub const Zone = struct {
|
|||||||
if (freed) return;
|
if (freed) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.smr) |smr| {
|
||||||
|
// Direct frees must synchronize, the object is instantly reusable.
|
||||||
|
_ = ke.smr.poll(smr, ke.smr.advance(smr), true);
|
||||||
|
}
|
||||||
|
|
||||||
self.lock.acquire();
|
self.lock.acquire();
|
||||||
self.slab_free(obj, true);
|
self.slab_free(obj, true);
|
||||||
self.lock.release();
|
self.lock.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn slab_free(self: *Self, obj: *anyopaque, poison: bool) void {
|
fn slab_free(self: *Self, obj: *anyopaque, poison: bool) void {
|
||||||
if (is_poison_enabled and poison) {
|
if (!is_poison_enabled or poison) {
|
||||||
if (self.dtor) |dtor| {
|
if (self.dtor) |dtor| {
|
||||||
dtor(obj);
|
dtor(obj);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_poison_enabled and poison) {
|
||||||
fill_with_poison(obj, self.obj_size, free_poison);
|
fill_with_poison(obj, self.obj_size, free_poison);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -961,6 +1020,10 @@ pub const Zone = struct {
|
|||||||
} else break;
|
} else break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (maglist.head == null) {
|
||||||
|
maglist.tail = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (last_removed) |last| {
|
if (last_removed) |last| {
|
||||||
last.next = null;
|
last.next = null;
|
||||||
} else {
|
} else {
|
||||||
@@ -1020,9 +1083,21 @@ pub const Zone = struct {
|
|||||||
|
|
||||||
/// Destroy a magazine.
|
/// Destroy a magazine.
|
||||||
fn magazine_destroy(self: *Self, magazine: *Magazine, rounds: usize) void {
|
fn magazine_destroy(self: *Self, magazine: *Magazine, rounds: usize) void {
|
||||||
|
if (rounds != 0) {
|
||||||
|
if (self.smr) |smr| {
|
||||||
|
// The stamp may still be uncommitted if the magazine never
|
||||||
|
// reached the zone depot.
|
||||||
|
ke.smr.deferred_advance_commit(smr, magazine.seq);
|
||||||
|
|
||||||
|
// Wait until we can destroy the magazine.
|
||||||
|
_ = ke.smr.poll(smr, magazine.seq, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Free its rounds to the slab layer.
|
// Free its rounds to the slab layer.
|
||||||
|
// For SMR zones the free-time work was deferred, do it now.
|
||||||
for (0..rounds) |i| {
|
for (0..rounds) |i| {
|
||||||
self.slab_free(magazine.rounds_ptr()[i], false);
|
self.slab_free(magazine.rounds_ptr()[i], self.smr != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free the magazine.
|
// Free the magazine.
|
||||||
@@ -1076,8 +1151,6 @@ pub const Zone = struct {
|
|||||||
const obj: *anyopaque = @ptrFromInt(slab.base + i * self.chunk_size);
|
const obj: *anyopaque = @ptrFromInt(slab.base + i * self.chunk_size);
|
||||||
if (is_poison_enabled) {
|
if (is_poison_enabled) {
|
||||||
fill_with_poison(obj, self.obj_size, free_poison);
|
fill_with_poison(obj, self.obj_size, free_poison);
|
||||||
} else {
|
|
||||||
if (self.ctor) |ctor| ctor(obj);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1121,8 +1194,6 @@ pub const Zone = struct {
|
|||||||
|
|
||||||
if (is_poison_enabled) {
|
if (is_poison_enabled) {
|
||||||
fill_with_poison(obj, self.obj_size, free_poison);
|
fill_with_poison(obj, self.obj_size, free_poison);
|
||||||
} else {
|
|
||||||
if (self.ctor) |ctor| ctor(obj);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1138,15 +1209,6 @@ pub const Zone = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn slab_destroy(self: *Self, slab: *Slab) void {
|
fn slab_destroy(self: *Self, slab: *Slab) void {
|
||||||
if (!is_poison_enabled) {
|
|
||||||
if (self.dtor) |dtor| {
|
|
||||||
for (0..slab.capacity) |i| {
|
|
||||||
const obj: *anyopaque = @ptrFromInt(slab.base + i * self.chunk_size);
|
|
||||||
dtor(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.obj_size > small_slab_size) {
|
if (self.obj_size > small_slab_size) {
|
||||||
const alloc_size = @sizeOf(Slab) + Slab.bitmap_bytes(slab.capacity);
|
const alloc_size = @sizeOf(Slab) + Slab.bitmap_bytes(slab.capacity);
|
||||||
gpa.free(@as([*]u8, @ptrCast(slab))[0..alloc_size]);
|
gpa.free(@as([*]u8, @ptrCast(slab))[0..alloc_size]);
|
||||||
@@ -1157,6 +1219,56 @@ pub const Zone = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check that a full magazine can be taken from the list head.
|
||||||
|
fn full_mags_ready(self: *Self, list: *MagazineList) bool {
|
||||||
|
const head = list.head orelse return false;
|
||||||
|
const smr = self.smr orelse return true;
|
||||||
|
|
||||||
|
return ke.smr.poll(smr, head.seq, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deferred free-time work for SMR zones, ran when a full magazine
|
||||||
|
/// is reused after its grace period expired.
|
||||||
|
fn magazine_reuse(self: *Self, mag: *Magazine) void {
|
||||||
|
if (!is_poison_enabled or self.smr == null) return;
|
||||||
|
|
||||||
|
for (0..magazine_size.load()) |i| {
|
||||||
|
const obj = mag.rounds_ptr()[i];
|
||||||
|
|
||||||
|
if (self.dtor) |dtor| {
|
||||||
|
dtor(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
fill_with_poison(obj, self.obj_size, free_poison);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move all local full magazines to the zone depot so their sequences
|
||||||
|
/// can age, committing their deferred advances. Depot lock is held.
|
||||||
|
fn smr_rotate_full(self: *Self, smr: *ke.smr.Domain, cpu: *Cpu) void {
|
||||||
|
const src = &cpu.depot.full_mags;
|
||||||
|
const dst = &self.depot.full_mags;
|
||||||
|
const tail = src.tail orelse return;
|
||||||
|
|
||||||
|
// Splice the whole list onto the zone depot.
|
||||||
|
if (dst.tail) |t| {
|
||||||
|
t.next = src.head;
|
||||||
|
} else {
|
||||||
|
dst.head = src.head;
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.tail = tail;
|
||||||
|
dst.num += src.num;
|
||||||
|
|
||||||
|
src.head = null;
|
||||||
|
src.tail = null;
|
||||||
|
src.num = 0;
|
||||||
|
src.min = 0;
|
||||||
|
|
||||||
|
// The last magazine has the newest stamp, committing it covers them all.
|
||||||
|
ke.smr.deferred_advance_commit(smr, tail.seq);
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to rebalance the zone and cpu depots w.r.t to each other after an allocation.
|
/// Try to rebalance the zone and cpu depots w.r.t to each other after an allocation.
|
||||||
/// The CPU depot moves its extra empty magazines to the zone depot, and tries
|
/// The CPU depot moves its extra empty magazines to the zone depot, and tries
|
||||||
/// to get full magazines from the zone depot up to `target`. CPU lock is held.
|
/// to get full magazines from the zone depot up to `target`. CPU lock is held.
|
||||||
@@ -1176,10 +1288,23 @@ pub const Zone = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.smr) |smr| {
|
||||||
|
// We got here because the local head hasn't expired or there is
|
||||||
|
// no local magazine at all, rotate the local magazines through the
|
||||||
|
// zone depot to let their sequences age.
|
||||||
|
// We then hope that the head of the zone depot has expired, otherwise we just return
|
||||||
|
// and fall back to the slab layer.
|
||||||
|
self.smr_rotate_full(smr, cpu);
|
||||||
|
}
|
||||||
|
|
||||||
// Grab a batch of full magazines from the zone depot.
|
// Grab a batch of full magazines from the zone depot.
|
||||||
const n = @min(target - cpu.depot.empty_mags.num, self.depot.full_mags.num);
|
// Exchange at least one magazine even when the depot size is 0.
|
||||||
for (0..n) |_| {
|
const n = @min(@max(target, 1) - cpu.depot.empty_mags.num, self.depot.full_mags.num);
|
||||||
Depot.free(Depot.alloc(&self.depot.full_mags).?, &cpu.depot.full_mags, self.reuse_policy);
|
|
||||||
|
if (n != 0 and self.full_mags_ready(&self.depot.full_mags)) {
|
||||||
|
for (0..n) |_| {
|
||||||
|
Depot.free(Depot.alloc(&self.depot.full_mags).?, &cpu.depot.full_mags, self.reuse_policy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.depot_lock.release_no_ipl();
|
self.depot_lock.release_no_ipl();
|
||||||
@@ -1196,16 +1321,31 @@ pub const Zone = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cpu.depot.full_mags.num >= target) {
|
if (cpu.depot.full_mags.num >= target) {
|
||||||
// We have excess empty magazines, move them to the zone depot.
|
if (self.smr) |smr| {
|
||||||
const n = cpu.depot.full_mags.num - target / 2;
|
// Rotate all full magazines through the zone depot so their
|
||||||
|
// sequences age, then try pulling back expired ones.
|
||||||
|
self.smr_rotate_full(smr, cpu);
|
||||||
|
|
||||||
for (0..n) |_| {
|
const n = @min(target / 2, self.depot.full_mags.num);
|
||||||
Depot.free(Depot.alloc(&cpu.depot.full_mags).?, &self.depot.full_mags, self.reuse_policy);
|
|
||||||
|
if (n != 0 and self.full_mags_ready(&self.depot.full_mags)) {
|
||||||
|
for (0..n) |_| {
|
||||||
|
Depot.free(Depot.alloc(&self.depot.full_mags).?, &cpu.depot.full_mags, self.reuse_policy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We have excess full magazines, move them to the zone depot.
|
||||||
|
const n = cpu.depot.full_mags.num - target / 2;
|
||||||
|
|
||||||
|
for (0..n) |_| {
|
||||||
|
Depot.free(Depot.alloc(&cpu.depot.full_mags).?, &self.depot.full_mags, self.reuse_policy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grab a batch of empty magazines from the zone depot.
|
// Grab a batch of empty magazines from the zone depot.
|
||||||
const n = @min(target - cpu.depot.full_mags.num, self.depot.empty_mags.num);
|
// Exchange at least one magazine even when the depot size is 0.
|
||||||
|
const n = @min(@max(target, 1) - cpu.depot.full_mags.num, self.depot.empty_mags.num);
|
||||||
for (0..n) |_| {
|
for (0..n) |_| {
|
||||||
Depot.free(Depot.alloc(&self.depot.empty_mags).?, &cpu.depot.empty_mags, self.reuse_policy);
|
Depot.free(Depot.alloc(&self.depot.empty_mags).?, &cpu.depot.empty_mags, self.reuse_policy);
|
||||||
}
|
}
|
||||||
@@ -1305,6 +1445,17 @@ pub fn late_init() linksection(r.init) void {
|
|||||||
magazines_initialized = true;
|
magazines_initialized = true;
|
||||||
update_work_item.init(.Normal, update, null);
|
update_work_item.init(.Normal, update, null);
|
||||||
ex.work.enqueue_in(&update_work_item, update_interval);
|
ex.work.enqueue_in(&update_work_item, update_interval);
|
||||||
|
|
||||||
|
smr_zone.init("SMR", .{});
|
||||||
|
smr_cpu_zone.init("SMR CPU", @sizeOf(ke.smr.Cpu) * ke.ncpus, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn smr_domain_create() !*ke.smr.Domain {
|
||||||
|
var dom: *ke.smr.Domain = try smr_zone.create();
|
||||||
|
dom.cpus = @ptrCast(@alignCast(try smr_cpu_zone.alloc(.{})));
|
||||||
|
dom.init();
|
||||||
|
|
||||||
|
return dom;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drain() void {
|
pub fn drain() void {
|
||||||
|
|||||||
Reference in New Issue
Block a user