Skip to content

Commit a1714dc

Browse files
committed
IoUring: futex operations
Signed-off-by: Bernard Assan <mega.alpha100@gmail.com> 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 Signed-off-by: Bernard Assan <mega.alpha100@gmail.com> 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 <mega.alpha100@gmail.com>
1 parent 6a4cee3 commit a1714dc

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
@@ -725,13 +725,13 @@ pub fn futex_4arg(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32, timeout
725725
);
726726
}
727727

728-
/// Given an array of `futex2_waitone`, wait on each uaddr.
728+
/// Given an array of `Futex2.WaitOne`, wait on each uaddr.
729729
/// The thread wakes if a futex_wake() is performed at any uaddr.
730730
/// The syscall returns immediately if any futex has *uaddr != val.
731731
/// timeout is an optional, absolute timeout value for the operation.
732732
/// The `flags` argument is for future use and currently should be `.{}`.
733733
/// Flags for private futexes, sizes, etc. should be set on the
734-
/// individual flags of each `futex2_waitone`.
734+
/// individual flags of each `Futex2.WaitOne`.
735735
///
736736
/// Returns the array index of one of the woken futexes.
737737
/// No further information is provided: any number of other futexes may also
@@ -741,20 +741,21 @@ pub fn futex_4arg(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32, timeout
741741
/// most recently woken, nor...)
742742
///
743743
/// Requires at least kernel v5.16.
744+
// TODO: can't we use slices here? and assert `Futex2.waitone_max`
744745
pub fn futex2_waitv(
745-
futexes: [*]const futex2_waitone,
746-
/// Length of `futexes`. Max of FUTEX2_WAITONE_MAX.
747-
nr_futexes: u32,
748-
flags: FUTEX2_FLAGS_WAITV,
746+
/// The length of `futexes` slice must not exceed `Futex2.waitone_max`
747+
futexes: []const Futex2.WaitOne,
748+
flags: Futex2.Waitv,
749749
/// Optional absolute timeout. Always 64-bit, even on 32-bit platforms.
750750
timeout: ?*const kernel_timespec,
751751
/// Clock to be used for the timeout, realtime or monotonic.
752752
clockid: clockid_t,
753753
) usize {
754+
assert(futexes.len <= Futex2.waitone_max);
754755
return syscall5(
755756
.futex_waitv,
756757
@intFromPtr(futexes),
757-
nr_futexes,
758+
@intCast(futexes.len),
758759
@as(u32, @bitCast(flags)),
759760
@intFromPtr(timeout),
760761
@intFromEnum(clockid),
@@ -772,8 +773,8 @@ pub fn futex2_wait(
772773
/// Value of `uaddr`.
773774
val: usize,
774775
/// Bitmask to match against incoming wakeup masks. Must not be zero.
775-
mask: usize,
776-
flags: FUTEX2_FLAGS,
776+
mask: Futex2.Bitset,
777+
flags: Futex2.Wait,
777778
/// Optional absolute timeout. Always 64-bit, even on 32-bit platforms.
778779
timeout: ?*const kernel_timespec,
779780
/// Clock to be used for the timeout, realtime or monotonic.
@@ -783,7 +784,7 @@ pub fn futex2_wait(
783784
.futex_wait,
784785
@intFromPtr(uaddr),
785786
val,
786-
mask,
787+
@intFromEnum(mask),
787788
@as(u32, @bitCast(flags)),
788789
@intFromPtr(timeout),
789790
@intFromEnum(clockid),
@@ -799,16 +800,16 @@ pub fn futex2_wake(
799800
/// Futex to wake
800801
uaddr: *const anyopaque,
801802
/// Bitmask to match against waiters.
802-
mask: usize,
803+
mask: Futex2.Bitset,
803804
/// Maximum number of waiters on the futex to wake.
804805
nr_wake: i32,
805-
flags: FUTEX2_FLAGS,
806+
flags: Futex2.Wake,
806807
) usize {
807808
return syscall4(
808809
.futex_wake,
809810
@intFromPtr(uaddr),
810-
mask,
811-
@as(u32, @bitCast(nr_wake)),
811+
@intFromEnum(mask),
812+
@intCast(nr_wake),
812813
@as(u32, @bitCast(flags)),
813814
);
814815
}
@@ -817,11 +818,12 @@ pub fn futex2_wake(
817818
/// Identical to `FUTEX.CMP_REQUEUE`, except it is part of the futex2 family of calls.
818819
///
819820
/// Requires at least kernel v6.7.
821+
// TODO: test to ensure I didn't break it
820822
pub fn futex2_requeue(
821823
/// The source and destination futexes. Must be a 2-element array.
822-
waiters: [*]const futex2_waitone,
824+
waiters: *const [2]Futex2.WaitOne,
823825
/// Currently unused.
824-
flags: FUTEX2_FLAGS_REQUEUE,
826+
flags: Futex2.Requeue,
825827
/// Maximum number of waiters to wake on the source futex.
826828
nr_wake: i32,
827829
/// Maximum number of waiters to transfer to the destination futex.
@@ -831,8 +833,8 @@ pub fn futex2_requeue(
831833
.futex_requeue,
832834
@intFromPtr(waiters),
833835
@as(u32, @bitCast(flags)),
834-
@as(u32, @bitCast(nr_wake)),
835-
@as(u32, @bitCast(nr_requeue)),
836+
@intCast(nr_wake),
837+
@intCast(nr_requeue),
836838
);
837839
}
838840

@@ -3675,39 +3677,91 @@ pub const FUTEX_WAKE_OP_CMP = enum(u4) {
36753677
GE = 5,
36763678
};
36773679

3678-
/// Max numbers of elements in a `futex2_waitone` array.
3679-
pub const FUTEX2_WAITONE_MAX = 128;
3680+
pub const Futex2 = struct {
3681+
/// Max numbers of elements in a `futex_waitv` .ie `WaitOne` array
3682+
/// matches FUTEX_WAITV_MAX
3683+
pub const waitone_max = 128;
36803684

3681-
/// For futex v2 API, the size of the futex at the uaddr. v1 futex are
3682-
/// always implicitly U32. As of kernel v6.14, only U32 is implemented
3683-
/// for v2 futexes.
3684-
pub const FUTEX2_SIZE = enum(u2) {
3685-
U8 = 0,
3686-
U16 = 1,
3687-
U32 = 2,
3688-
U64 = 3,
3689-
};
3685+
/// For futex v2 API, the size of the futex at the uaddr. v1 futex are
3686+
/// always implicitly U32. As of kernel v6.14, only U32 is implemented
3687+
/// for v2 futexes.
3688+
pub const Size = enum(u2) {
3689+
U8 = 0,
3690+
U16 = 1,
3691+
U32 = 2,
3692+
U64 = 3,
3693+
};
36903694

3691-
/// As of kernel 6.14 there are no defined flags to futex2_waitv.
3692-
pub const FUTEX2_FLAGS_WAITV = packed struct(u32) {
3693-
_reserved: u32 = 0,
3694-
};
3695+
/// flags for `futex2_requeue` syscall
3696+
/// As of kernel 6.14 there are no defined flags to futex2_requeue.
3697+
pub const Requeue = packed struct(u32) {
3698+
_: u32 = 0,
3699+
};
36953700

3696-
/// As of kernel 6.14 there are no defined flags to futex2_requeue.
3697-
pub const FUTEX2_FLAGS_REQUEUE = packed struct(u32) {
3698-
_reserved: u32 = 0,
3699-
};
3701+
/// flags for `futex2_waitv` syscall
3702+
/// As of kernel 6.14 there are no defined flags to futex2_waitv.
3703+
pub const Waitv = packed struct(u32) {
3704+
_: u32 = 0,
3705+
};
37003706

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

3747+
/// DEPRECATED use `Futex2.WaitOne`
3748+
pub const futex2_waitone = Futex2.WaitOne;
3749+
3750+
/// DEPRECATED use constant in `Futex2`
3751+
pub const FUTEX2_WAITONE_MAX = Futex2.waitone_max;
3752+
3753+
/// DEPRECATED use `Size` type in `Futex2`
3754+
pub const FUTEX2_SIZE = Futex2.Size;
3755+
3756+
/// DEPRECATED use `Waitv` in `Futex2`
3757+
pub const FUTEX2_FLAGS_WAITV = Futex2.Waitv;
3758+
3759+
/// DEPRECATED use `Requeue` in `Futex2`
3760+
pub const FUTEX2_FLAGS_REQUEUE = Futex2.Requeue;
3761+
3762+
/// DEPRECATED use `Wait` in `Futex2`
3763+
pub const FUTEX2_FLAGS = Futex2.Wait;
3764+
37113765
pub const PROT = struct {
37123766
/// page can not be accessed
37133767
pub const NONE = 0x0;
@@ -9860,19 +9914,6 @@ pub const PTRACE = struct {
98609914
};
98619915
};
98629916

9863-
/// For futex2_waitv and futex2_requeue. Arrays of `futex2_waitone` allow
9864-
/// waiting on multiple futexes in one call.
9865-
pub const futex2_waitone = extern struct {
9866-
/// Expected value at uaddr, should match size of futex.
9867-
val: u64,
9868-
/// User address to wait on. Top-bits must be 0 on 32-bit.
9869-
uaddr: u64,
9870-
/// Flags for this waiter.
9871-
flags: FUTEX2_FLAGS,
9872-
/// Reserved member to preserve alignment.
9873-
__reserved: u32 = 0,
9874-
};
9875-
98769917
pub const cache_stat_range = extern struct {
98779918
off: u64,
98789919
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.
@@ -1773,6 +1773,54 @@ pub fn waitid(
17731773
return sqe;
17741774
}
17751775

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

@@ -3220,6 +3268,61 @@ pub const Sqe = extern struct {
32203268
sqe.splice_fd_in = @bitCast(options);
32213269
}
32223270

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

0 commit comments

Comments
 (0)