Skip to content

Commit e0f8ccb

Browse files
committed
IoUring: futex operations
Signed-off-by: Bernard Assan <[email protected]> Add futex_* operations to IoUring Refactor Futex 2 flags into Futex2 struct add mpol to Wait flags and fix private field as its 128 not 32 TODO: verify flags for all futex2 types are valid Signed-off-by: Bernard Assan <[email protected]> Update futex2_* functions and constants to use the new Futex2 type Improve the Api of futex2_* functions to be more idiomatic Zig Signed-off-by: Bernard Assan <[email protected]>
1 parent 8eda458 commit e0f8ccb

File tree

2 files changed

+203
-59
lines changed

2 files changed

+203
-59
lines changed

lib/std/os/linux.zig

Lines changed: 99 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -724,13 +724,13 @@ pub fn futex_4arg(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32, timeout
724724
);
725725
}
726726

727-
/// Given an array of `futex2_waitone`, wait on each uaddr.
727+
/// Given an array of `Futex2.WaitOne`, wait on each uaddr.
728728
/// The thread wakes if a futex_wake() is performed at any uaddr.
729729
/// The syscall returns immediately if any futex has *uaddr != val.
730730
/// timeout is an optional, absolute timeout value for the operation.
731731
/// The `flags` argument is for future use and currently should be `.{}`.
732732
/// Flags for private futexes, sizes, etc. should be set on the
733-
/// individual flags of each `futex2_waitone`.
733+
/// individual flags of each `Futex2.WaitOne`.
734734
///
735735
/// Returns the array index of one of the woken futexes.
736736
/// No further information is provided: any number of other futexes may also
@@ -740,20 +740,21 @@ pub fn futex_4arg(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32, timeout
740740
/// most recently woken, nor...)
741741
///
742742
/// Requires at least kernel v5.16.
743+
// TODO: can't we use slices here? and assert `Futex2.waitone_max`
743744
pub fn futex2_waitv(
744-
futexes: [*]const futex2_waitone,
745-
/// Length of `futexes`. Max of FUTEX2_WAITONE_MAX.
746-
nr_futexes: u32,
747-
flags: FUTEX2_FLAGS_WAITV,
745+
/// The length of `futexes` slice must not exceed `Futex2.waitone_max`
746+
futexes: []const Futex2.WaitOne,
747+
flags: Futex2.Waitv,
748748
/// Optional absolute timeout. Always 64-bit, even on 32-bit platforms.
749749
timeout: ?*const kernel_timespec,
750750
/// Clock to be used for the timeout, realtime or monotonic.
751751
clockid: clockid_t,
752752
) usize {
753+
assert(futexes.len <= Futex2.waitone_max);
753754
return syscall5(
754755
.futex_waitv,
755756
@intFromPtr(futexes),
756-
nr_futexes,
757+
@intCast(futexes.len),
757758
@as(u32, @bitCast(flags)),
758759
@intFromPtr(timeout),
759760
@intFromEnum(clockid),
@@ -771,8 +772,8 @@ pub fn futex2_wait(
771772
/// Value of `uaddr`.
772773
val: usize,
773774
/// Bitmask to match against incoming wakeup masks. Must not be zero.
774-
mask: usize,
775-
flags: FUTEX2_FLAGS,
775+
mask: Futex2.Bitset,
776+
flags: Futex2.Wait,
776777
/// Optional absolute timeout. Always 64-bit, even on 32-bit platforms.
777778
timeout: ?*const kernel_timespec,
778779
/// Clock to be used for the timeout, realtime or monotonic.
@@ -782,7 +783,7 @@ pub fn futex2_wait(
782783
.futex_wait,
783784
@intFromPtr(uaddr),
784785
val,
785-
mask,
786+
@intFromEnum(mask),
786787
@as(u32, @bitCast(flags)),
787788
@intFromPtr(timeout),
788789
@intFromEnum(clockid),
@@ -798,16 +799,16 @@ pub fn futex2_wake(
798799
/// Futex to wake
799800
uaddr: *const anyopaque,
800801
/// Bitmask to match against waiters.
801-
mask: usize,
802+
mask: Futex2.Bitset,
802803
/// Maximum number of waiters on the futex to wake.
803804
nr_wake: i32,
804-
flags: FUTEX2_FLAGS,
805+
flags: Futex2.Wake,
805806
) usize {
806807
return syscall4(
807808
.futex_wake,
808809
@intFromPtr(uaddr),
809-
mask,
810-
@as(u32, @bitCast(nr_wake)),
810+
@intFromEnum(mask),
811+
@intCast(nr_wake),
811812
@as(u32, @bitCast(flags)),
812813
);
813814
}
@@ -816,11 +817,12 @@ pub fn futex2_wake(
816817
/// Identical to `FUTEX.CMP_REQUEUE`, except it is part of the futex2 family of calls.
817818
///
818819
/// Requires at least kernel v6.7.
820+
// TODO: test to ensure I didn't break it
819821
pub fn futex2_requeue(
820822
/// The source and destination futexes. Must be a 2-element array.
821-
waiters: [*]const futex2_waitone,
823+
waiters: *const [2]Futex2.WaitOne,
822824
/// Currently unused.
823-
flags: FUTEX2_FLAGS_REQUEUE,
825+
flags: Futex2.Requeue,
824826
/// Maximum number of waiters to wake on the source futex.
825827
nr_wake: i32,
826828
/// Maximum number of waiters to transfer to the destination futex.
@@ -830,8 +832,8 @@ pub fn futex2_requeue(
830832
.futex_requeue,
831833
@intFromPtr(waiters),
832834
@as(u32, @bitCast(flags)),
833-
@as(u32, @bitCast(nr_wake)),
834-
@as(u32, @bitCast(nr_requeue)),
835+
@intCast(nr_wake),
836+
@intCast(nr_requeue),
835837
);
836838
}
837839

@@ -3666,39 +3668,91 @@ pub const FUTEX_WAKE_OP_CMP = enum(u4) {
36663668
GE = 5,
36673669
};
36683670

3669-
/// Max numbers of elements in a `futex2_waitone` array.
3670-
pub const FUTEX2_WAITONE_MAX = 128;
3671+
pub const Futex2 = struct {
3672+
/// Max numbers of elements in a `futex_waitv` .ie `WaitOne` array
3673+
/// matches FUTEX_WAITV_MAX
3674+
pub const waitone_max = 128;
36713675

3672-
/// For futex v2 API, the size of the futex at the uaddr. v1 futex are
3673-
/// always implicitly U32. As of kernel v6.14, only U32 is implemented
3674-
/// for v2 futexes.
3675-
pub const FUTEX2_SIZE = enum(u2) {
3676-
U8 = 0,
3677-
U16 = 1,
3678-
U32 = 2,
3679-
U64 = 3,
3680-
};
3676+
/// For futex v2 API, the size of the futex at the uaddr. v1 futex are
3677+
/// always implicitly U32. As of kernel v6.14, only U32 is implemented
3678+
/// for v2 futexes.
3679+
pub const Size = enum(u2) {
3680+
U8 = 0,
3681+
U16 = 1,
3682+
U32 = 2,
3683+
U64 = 3,
3684+
};
36813685

3682-
/// As of kernel 6.14 there are no defined flags to futex2_waitv.
3683-
pub const FUTEX2_FLAGS_WAITV = packed struct(u32) {
3684-
_reserved: u32 = 0,
3685-
};
3686+
/// flags for `futex2_requeue` syscall
3687+
/// As of kernel 6.14 there are no defined flags to futex2_requeue.
3688+
pub const Requeue = packed struct(u32) {
3689+
_: u32 = 0,
3690+
};
36863691

3687-
/// As of kernel 6.14 there are no defined flags to futex2_requeue.
3688-
pub const FUTEX2_FLAGS_REQUEUE = packed struct(u32) {
3689-
_reserved: u32 = 0,
3690-
};
3692+
/// flags for `futex2_waitv` syscall
3693+
/// As of kernel 6.14 there are no defined flags to futex2_waitv.
3694+
pub const Waitv = packed struct(u32) {
3695+
_: u32 = 0,
3696+
};
36913697

3692-
/// Flags for futex v2 APIs (futex2_wait, futex2_wake, futex2_requeue, but
3693-
/// not the futex2_waitv syscall, but also used in the futex2_waitone struct).
3694-
pub const FUTEX2_FLAGS = packed struct(u32) {
3695-
size: FUTEX2_SIZE,
3696-
numa: bool = false,
3697-
_reserved: u4 = 0,
3698-
private: bool,
3699-
_undefined: u24 = 0,
3698+
/// flags for `futex2_wait` syscall
3699+
// COMMIT: add mpol and fix private field as its 128 not 32
3700+
pub const Wait = packed struct(u32) {
3701+
size: Size,
3702+
numa: bool = false,
3703+
mpol: bool = false,
3704+
_5: u3 = 0,
3705+
private: bool,
3706+
_9: u24 = 0,
3707+
};
3708+
3709+
/// flags for `futex2_wake` syscall
3710+
pub const Wake = Wait;
3711+
3712+
/// A waiter for vectorized wait
3713+
/// For `futex2_waitv` and `futex2_requeue`. Arrays of `WaitOne`
3714+
/// allow waiting on multiple futexes in one call.
3715+
/// matches `futex_waitv` in kernel
3716+
pub const WaitOne = extern struct {
3717+
/// Expected value at uaddr, should match size of futex.
3718+
val: u64,
3719+
/// User address to wait on. Top-bits must be 0 on 32-bit.
3720+
uaddr: u64,
3721+
/// Flags for this waiter.
3722+
flags: Wait,
3723+
/// Reserved member to preserve data alignment.
3724+
__reserved: u32 = 0,
3725+
};
3726+
3727+
pub const Bitset = enum(u64) {
3728+
/// matches FUTEX_WAIT_BITSET
3729+
wait = 9,
3730+
/// matches FUTEX_WAKE_BITSET
3731+
wake = 10,
3732+
/// bitset with all bits set for the FUTEX_xxx_BITSET OPs to request a
3733+
/// match of any bit.
3734+
match_any = 0xffffffff,
3735+
};
37003736
};
37013737

3738+
/// DEPRECATED use `Futex2.WaitOne`
3739+
pub const futex2_waitone = Futex2.WaitOne;
3740+
3741+
/// DEPRECATED use constant in `Futex2`
3742+
pub const FUTEX2_WAITONE_MAX = Futex2.waitone_max;
3743+
3744+
/// DEPRECATED use `Size` type in `Futex2`
3745+
pub const FUTEX2_SIZE = Futex2.Size;
3746+
3747+
/// DEPRECATED use `Waitv` in `Futex2`
3748+
pub const FUTEX2_FLAGS_WAITV = Futex2.Waitv;
3749+
3750+
/// DEPRECATED use `Requeue` in `Futex2`
3751+
pub const FUTEX2_FLAGS_REQUEUE = Futex2.Requeue;
3752+
3753+
/// DEPRECATED use `Wait` in `Futex2`
3754+
pub const FUTEX2_FLAGS = Futex2.Wait;
3755+
37023756
pub const PROT = struct {
37033757
/// page can not be accessed
37043758
pub const NONE = 0x0;
@@ -9848,19 +9902,6 @@ pub const PTRACE = struct {
98489902
};
98499903
};
98509904

9851-
/// For futex2_waitv and futex2_requeue. Arrays of `futex2_waitone` allow
9852-
/// waiting on multiple futexes in one call.
9853-
pub const futex2_waitone = extern struct {
9854-
/// Expected value at uaddr, should match size of futex.
9855-
val: u64,
9856-
/// User address to wait on. Top-bits must be 0 on 32-bit.
9857-
uaddr: u64,
9858-
/// Flags for this waiter.
9859-
flags: FUTEX2_FLAGS,
9860-
/// Reserved member to preserve alignment.
9861-
__reserved: u32 = 0,
9862-
};
9863-
98649905
pub const cache_stat_range = extern struct {
98659906
off: u64,
98669907
len: u64,

lib/std/os/linux/IoUring.zig

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ pub fn cq_ready(self: *IoUring) u32 {
295295

296296
/// Copies as many CQEs as are ready, and that can fit into the destination
297297
/// `cqes` slice. If none are available, enters into the kernel to wait for at
298-
/// most `wait_nr` CQEs.
298+
/// least `wait_nr` CQEs.
299299
/// Returns the number of CQEs copied, advancing the CQ ring.
300300
/// Provides all the wait/peek methods found in liburing, but with batching and
301301
/// a single method.
@@ -1767,6 +1767,54 @@ pub fn waitid(
17671767
return sqe;
17681768
}
17691769

1770+
/// Available since kernel 6.7
1771+
pub fn futex_wake(
1772+
self: *IoUring,
1773+
user_data: u64,
1774+
futex: *u32,
1775+
max_wake_count: u64,
1776+
mask: linux.Futex2.Bitset,
1777+
futex_flags: linux.Futex2.Wake,
1778+
flags: u32, // They are currently unused, and hence 0 should be passed
1779+
) !*Sqe {
1780+
const sqe = try self.get_sqe();
1781+
sqe.prep_futex_wake(futex, max_wake_count, mask, futex_flags, flags);
1782+
sqe.user_data = user_data;
1783+
return sqe;
1784+
}
1785+
1786+
/// Available since kernel 6.7
1787+
pub fn futex_wait(
1788+
self: *IoUring,
1789+
user_data: u64,
1790+
futex: *u32,
1791+
max_wake_count: u64,
1792+
mask: linux.Futex2.Bitset,
1793+
futex_flags: linux.Futex2.Wait,
1794+
/// They are currently unused, and hence 0 should be passed
1795+
flags: u32,
1796+
) !*Sqe {
1797+
const sqe = try self.get_sqe();
1798+
sqe.prep_futex_wait(futex, max_wake_count, mask, futex_flags, flags);
1799+
sqe.user_data = user_data;
1800+
return sqe;
1801+
}
1802+
1803+
// TODO: ensure flags and Wait in futexv are correct
1804+
/// Available since kernel 6.7
1805+
pub fn futex_waitv(
1806+
self: *IoUring,
1807+
user_data: u64,
1808+
futexv: []linux.Futex2.WaitOne,
1809+
/// They are currently unused, and hence 0 should be passed
1810+
flags: u32,
1811+
) !*Sqe {
1812+
const sqe = try self.get_sqe();
1813+
sqe.prep_futex_waitv(futexv, flags);
1814+
sqe.user_data = user_data;
1815+
return sqe;
1816+
}
1817+
17701818
pub fn register_buffers_sparse(self: *IoUring, nr: u32) !void {
17711819
assert(self.fd >= 0);
17721820

@@ -3211,6 +3259,61 @@ pub const Sqe = extern struct {
32113259
sqe.splice_fd_in = @bitCast(options);
32123260
}
32133261

3262+
pub fn prep_futex_wake(
3263+
sqe: *Sqe,
3264+
futex: *u32,
3265+
max_wake_count: u64,
3266+
mask: linux.Futex2.Bitset,
3267+
futex_flags: linux.Futex2.Wake,
3268+
flags: u32, // They are currently unused, and hence 0 should be passed
3269+
) void {
3270+
sqe.prep_rw(
3271+
.futex_wake,
3272+
@intCast(@as(u32, @bitCast(futex_flags))),
3273+
@intFromPtr(futex),
3274+
0,
3275+
max_wake_count,
3276+
);
3277+
sqe.rw_flags = flags;
3278+
sqe.addr3 = @intFromEnum(mask);
3279+
}
3280+
3281+
pub fn prep_futex_wait(
3282+
sqe: *Sqe,
3283+
futex: *u32,
3284+
max_wake_count: u64,
3285+
mask: linux.Futex2.Bitset,
3286+
futex_flags: linux.Futex2.Wait,
3287+
/// They are currently unused, and hence 0 should be passed
3288+
flags: u32,
3289+
) void {
3290+
sqe.prep_rw(
3291+
.futex_wait,
3292+
@intCast(@as(u32, @bitCast(futex_flags))),
3293+
@intFromPtr(futex),
3294+
0,
3295+
max_wake_count,
3296+
);
3297+
sqe.rw_flags = flags;
3298+
sqe.addr3 = @intFromEnum(mask);
3299+
}
3300+
3301+
pub fn prep_futex_waitv(
3302+
sqe: *Sqe,
3303+
futexv: []linux.Futex2.WaitOne,
3304+
/// They are currently unused, and hence 0 should be passed
3305+
flags: u32,
3306+
) void {
3307+
sqe.prep_rw(
3308+
.futex_waitv,
3309+
0,
3310+
@intFromPtr(futexv.ptr),
3311+
futexv.len,
3312+
0,
3313+
);
3314+
sqe.rw_flags = flags;
3315+
}
3316+
32143317
// TODO: maybe remove unused flag fields?
32153318
pub fn prep_bind(
32163319
sqe: *Sqe,

0 commit comments

Comments
 (0)