mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
Add a Futex2 Bitset type for futex2 wake and wait syscalls
Cleanup linux/test.zig type futex/2_* uaddr as *const u32 consider changing to *const atomic.Value(u32) Use At Flags in fstatat Use EpollOp in epoll_ctl syscall Signed-off-by: Bernard Assan <mega.alpha100@gmail.com>
This commit is contained in:
parent
35bbe99cb2
commit
be63611c64
3 changed files with 138 additions and 61 deletions
|
|
@ -1663,7 +1663,7 @@ const LinuxThreadImpl = struct {
|
|||
if (tid == 0) break;
|
||||
|
||||
switch (linux.errno(linux.futex_4arg(
|
||||
&self.thread.child_tid.raw,
|
||||
@ptrCast(&self.thread.child_tid.raw),
|
||||
.{ .cmd = .WAIT, .private = false },
|
||||
@bitCast(tid),
|
||||
null,
|
||||
|
|
|
|||
|
|
@ -687,7 +687,14 @@ pub const futex_param4 = extern union {
|
|||
///
|
||||
/// The futex_op parameter is a sub-command and flags. The sub-command
|
||||
/// defines which of the subsequent paramters are relevant.
|
||||
pub fn futex(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32, val2timeout: futex_param4, uaddr2: ?*const anyopaque, val3: u32) usize {
|
||||
pub fn futex(
|
||||
uaddr: *const u32,
|
||||
futex_op: FUTEX_OP,
|
||||
val: u32,
|
||||
val2timeout: futex_param4,
|
||||
uaddr2: ?*const anyopaque,
|
||||
val3: u32,
|
||||
) usize {
|
||||
return syscall6(
|
||||
if (@hasField(SYS, "futex") and native_arch != .hexagon) .futex else .futex_time64,
|
||||
@intFromPtr(uaddr),
|
||||
|
|
@ -701,7 +708,7 @@ pub fn futex(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32, val2timeout:
|
|||
|
||||
/// Three-argument variation of the v1 futex call. Only suitable for a
|
||||
/// futex_op that ignores the remaining arguments (e.g., FUTUX_OP.WAKE).
|
||||
pub fn futex_3arg(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32) usize {
|
||||
pub fn futex_3arg(uaddr: *const u32, futex_op: FUTEX_OP, val: u32) usize {
|
||||
return syscall3(
|
||||
if (@hasField(SYS, "futex") and native_arch != .hexagon) .futex else .futex_time64,
|
||||
@intFromPtr(uaddr),
|
||||
|
|
@ -712,7 +719,7 @@ pub fn futex_3arg(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32) usize {
|
|||
|
||||
/// Four-argument variation on the v1 futex call. Only suitable for
|
||||
/// futex_op that ignores the remaining arguments (e.g., FUTEX_OP.WAIT).
|
||||
pub fn futex_4arg(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32, timeout: ?*const timespec) usize {
|
||||
pub fn futex_4arg(uaddr: *const u32, futex_op: FUTEX_OP, val: u32, timeout: ?*const timespec) usize {
|
||||
return syscall4(
|
||||
if (@hasField(SYS, "futex") and native_arch != .hexagon) .futex else .futex_time64,
|
||||
@intFromPtr(uaddr),
|
||||
|
|
@ -751,7 +758,7 @@ pub fn futex2_waitv(
|
|||
assert(futexes.len <= Futex2.waitone_max);
|
||||
return syscall5(
|
||||
.futex_waitv,
|
||||
@intFromPtr(futexes),
|
||||
@intFromPtr(futexes.ptr),
|
||||
@intCast(futexes.len),
|
||||
@as(u32, @bitCast(flags)),
|
||||
@intFromPtr(timeout),
|
||||
|
|
@ -766,7 +773,7 @@ pub fn futex2_waitv(
|
|||
/// Requires at least kernel v6.7.
|
||||
pub fn futex2_wait(
|
||||
/// Address of the futex to wait on.
|
||||
uaddr: *const anyopaque,
|
||||
uaddr: *const u32,
|
||||
/// Value of `uaddr`.
|
||||
val: usize,
|
||||
/// Bitmask to match against incoming wakeup masks. Must not be zero.
|
||||
|
|
@ -781,7 +788,7 @@ pub fn futex2_wait(
|
|||
.futex_wait,
|
||||
@intFromPtr(uaddr),
|
||||
val,
|
||||
@intFromEnum(mask),
|
||||
@intCast(mask.toInt()),
|
||||
@as(u32, @bitCast(flags)),
|
||||
@intFromPtr(timeout),
|
||||
@intFromEnum(clockid),
|
||||
|
|
@ -795,7 +802,7 @@ pub fn futex2_wait(
|
|||
/// Requires at least kernel v6.7.
|
||||
pub fn futex2_wake(
|
||||
/// Futex to wake
|
||||
uaddr: *const anyopaque,
|
||||
uaddr: *const u32,
|
||||
/// Bitmask to match against waiters.
|
||||
mask: Futex2.Bitset,
|
||||
/// Maximum number of waiters on the futex to wake.
|
||||
|
|
@ -805,7 +812,7 @@ pub fn futex2_wake(
|
|||
return syscall4(
|
||||
.futex_wake,
|
||||
@intFromPtr(uaddr),
|
||||
@intFromEnum(mask),
|
||||
@intCast(mask.toInt()),
|
||||
@intCast(nr_wake),
|
||||
@as(u32, @bitCast(flags)),
|
||||
);
|
||||
|
|
@ -2240,16 +2247,27 @@ pub fn lstat(pathname: [*:0]const u8, statbuf: *Stat) usize {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: flags is At Flags
|
||||
pub fn fstatat(dirfd: i32, path: [*:0]const u8, stat_buf: *Stat, flags: u32) usize {
|
||||
pub fn fstatat(dirfd: i32, path: [*:0]const u8, stat_buf: *Stat, flags: At) usize {
|
||||
if (native_arch == .riscv32 or native_arch.isLoongArch()) {
|
||||
// riscv32 and loongarch have made the interesting decision to not implement some of
|
||||
// the older stat syscalls, including this one.
|
||||
@compileError("No fstatat syscall on this architecture.");
|
||||
} else if (@hasField(SYS, "fstatat64")) {
|
||||
return syscall4(.fstatat64, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), @intFromPtr(stat_buf), flags);
|
||||
return syscall4(
|
||||
.fstatat64,
|
||||
@as(usize, @bitCast(@as(isize, dirfd))),
|
||||
@intFromPtr(path),
|
||||
@intFromPtr(stat_buf),
|
||||
@intCast(@as(u32, @bitCast(flags))),
|
||||
);
|
||||
} else {
|
||||
return syscall4(.fstatat, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), @intFromPtr(stat_buf), flags);
|
||||
return syscall4(
|
||||
.fstatat,
|
||||
@as(usize, @bitCast(@as(isize, dirfd))),
|
||||
@intFromPtr(path),
|
||||
@intFromPtr(stat_buf),
|
||||
@bitCast(flags),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2419,8 +2437,14 @@ pub fn epoll_create1(flags: usize) usize {
|
|||
return syscall1(.epoll_create1, flags);
|
||||
}
|
||||
|
||||
pub fn epoll_ctl(epoll_fd: i32, op: u32, fd: i32, ev: ?*epoll_event) usize {
|
||||
return syscall4(.epoll_ctl, @as(usize, @bitCast(@as(isize, epoll_fd))), @as(usize, @intCast(op)), @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(ev));
|
||||
pub fn epoll_ctl(epoll_fd: i32, op: EpollOp, fd: i32, ev: ?*epoll_event) usize {
|
||||
return syscall4(
|
||||
.epoll_ctl,
|
||||
@as(usize, @bitCast(@as(isize, epoll_fd))),
|
||||
@as(usize, @intFromEnum(op)),
|
||||
@as(usize, @bitCast(@as(isize, fd))),
|
||||
@intFromPtr(ev),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn epoll_wait(epoll_fd: i32, events: [*]epoll_event, maxevents: u32, timeout: i32) usize {
|
||||
|
|
@ -3687,14 +3711,67 @@ pub const Futex2 = struct {
|
|||
__reserved: u32 = 0,
|
||||
};
|
||||
|
||||
pub const Bitset = enum(u64) {
|
||||
/// matches FUTEX_WAIT_BITSET
|
||||
wait = 9,
|
||||
/// matches FUTEX_WAKE_BITSET
|
||||
wake = 10,
|
||||
/// bitset with all bits set for the FUTEX_xxx_BITSET OPs to request a
|
||||
/// match of any bit.
|
||||
match_any = 0xffffffff,
|
||||
/// `Bitset` for `futex2_wait`, `futex2_wake`, `IoUring.futex_wait` and
|
||||
/// `IoUring.futex_wake` operations
|
||||
/// At least one bit must be set before performing supported operations
|
||||
/// The bitset is stored in the kernel-internal state of a waiter. During a
|
||||
/// wake operation, the same mask previously set during the wait call can
|
||||
/// be used to select which waiters to woke up
|
||||
/// See https://man7.org/linux/man-pages/man2/futex_wake_bitset.2const.html
|
||||
/// `IoUring` supports a u64 `Bitset` while the raw syscalls uses only u32
|
||||
/// bits of `Bitset`
|
||||
pub const Bitset = packed struct(u64) {
|
||||
waiter1: bool = false,
|
||||
waiter2: bool = false,
|
||||
waiter3: bool = false,
|
||||
waiter4: bool = false,
|
||||
waiter5: bool = false,
|
||||
waiter6: bool = false,
|
||||
waiter7: bool = false,
|
||||
waiter8: bool = false,
|
||||
waiter9: bool = false,
|
||||
waiter10: bool = false,
|
||||
waiter11: bool = false,
|
||||
waiter12: bool = false,
|
||||
waiter13: bool = false,
|
||||
waiter14: bool = false,
|
||||
waiter15: bool = false,
|
||||
waiter16: bool = false,
|
||||
waiter17: bool = false,
|
||||
waiter18: bool = false,
|
||||
waiter19: bool = false,
|
||||
waiter20: bool = false,
|
||||
waiter21: bool = false,
|
||||
waiter22: bool = false,
|
||||
waiter23: bool = false,
|
||||
waiter24: bool = false,
|
||||
waiter25: bool = false,
|
||||
waiter26: bool = false,
|
||||
waiter27: bool = false,
|
||||
waiter28: bool = false,
|
||||
waiter29: bool = false,
|
||||
waiter30: bool = false,
|
||||
waiter31: bool = false,
|
||||
waiter32: bool = false,
|
||||
io_uring_extra: u32 = 0,
|
||||
|
||||
/// `Bitset` with all bits set for the FUTEX_xxx_BITSET OPs to request a
|
||||
/// match of any bit. matches FUTEX_BITSET_MATCH_ANY
|
||||
pub const match_any: Bitset = @bitCast(@as(u64, 0x00000000ffffffff));
|
||||
/// Bitset must not be empty, this is only useful in test
|
||||
pub const empty: Bitset = .{};
|
||||
|
||||
/// Create from raw u64 value
|
||||
pub fn fromInt(value: u64) Bitset {
|
||||
const bitset: Bitset = @bitCast(value);
|
||||
assert(bitset != empty);
|
||||
return bitset;
|
||||
}
|
||||
|
||||
/// Convert to raw u64 for syscall
|
||||
pub fn toInt(self: Bitset) u64 {
|
||||
return @bitCast(self);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -45,34 +45,33 @@ test "timer" {
|
|||
var err: linux.E = linux.errno(epoll_fd);
|
||||
try expect(err == .SUCCESS);
|
||||
|
||||
const timer_fd = linux.timerfd_create(linux.TIMERFD_CLOCK.MONOTONIC, .{});
|
||||
const timer_fd = linux.timerfd_create(.MONOTONIC, .{});
|
||||
try expect(linux.errno(timer_fd) == .SUCCESS);
|
||||
|
||||
const time_interval = linux.timespec{
|
||||
const time_interval: linux.timespec = .{
|
||||
.sec = 0,
|
||||
.nsec = 2000000,
|
||||
};
|
||||
|
||||
const new_time = linux.itimerspec{
|
||||
const new_time: linux.itimerspec = .{
|
||||
.it_interval = time_interval,
|
||||
.it_value = time_interval,
|
||||
};
|
||||
|
||||
err = linux.errno(linux.timerfd_settime(@as(i32, @intCast(timer_fd)), .{}, &new_time, null));
|
||||
err = linux.errno(linux.timerfd_settime(@intCast(timer_fd), .{}, &new_time, null));
|
||||
try expect(err == .SUCCESS);
|
||||
|
||||
var event = linux.epoll_event{
|
||||
var event: linux.epoll_event = .{
|
||||
.events = linux.EPOLL.IN | linux.EPOLL.OUT | linux.EPOLL.ET,
|
||||
.data = linux.epoll_data{ .ptr = 0 },
|
||||
.data = .{ .ptr = 0 },
|
||||
};
|
||||
|
||||
err = linux.errno(linux.epoll_ctl(@as(i32, @intCast(epoll_fd)), linux.EPOLL.CTL_ADD, @as(i32, @intCast(timer_fd)), &event));
|
||||
err = linux.errno(linux.epoll_ctl(@intCast(epoll_fd), .ctl_add, @intCast(timer_fd), &event));
|
||||
try expect(err == .SUCCESS);
|
||||
|
||||
const events_one: linux.epoll_event = undefined;
|
||||
var events = [_]linux.epoll_event{events_one} ** 8;
|
||||
var events: [8]linux.epoll_event = @splat(undefined);
|
||||
|
||||
err = linux.errno(linux.epoll_wait(@as(i32, @intCast(epoll_fd)), &events, 8, -1));
|
||||
err = linux.errno(linux.epoll_wait(@intCast(epoll_fd), &events, 8, -1));
|
||||
try expect(err == .SUCCESS);
|
||||
}
|
||||
|
||||
|
|
@ -99,11 +98,11 @@ test "statx" {
|
|||
}
|
||||
|
||||
try expect(stat_buf.mode == statx_buf.mode);
|
||||
try expect(@as(u32, @bitCast(stat_buf.uid)) == statx_buf.uid);
|
||||
try expect(@as(u32, @bitCast(stat_buf.gid)) == statx_buf.gid);
|
||||
try expect(@as(u64, @bitCast(@as(i64, stat_buf.size))) == statx_buf.size);
|
||||
try expect(@as(u64, @bitCast(@as(i64, stat_buf.blksize))) == statx_buf.blksize);
|
||||
try expect(@as(u64, @bitCast(@as(i64, stat_buf.blocks))) == statx_buf.blocks);
|
||||
try expect(stat_buf.uid == statx_buf.uid);
|
||||
try expect(stat_buf.gid == statx_buf.gid);
|
||||
try expect(stat_buf.size == statx_buf.size);
|
||||
try expect(stat_buf.blksize == statx_buf.blksize);
|
||||
try expect(stat_buf.blocks == statx_buf.blocks);
|
||||
}
|
||||
|
||||
test "user and group ids" {
|
||||
|
|
@ -190,14 +189,14 @@ comptime {
|
|||
assert(256 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = false, .realtime = true })));
|
||||
|
||||
// Check futex_param4 union is packed correctly
|
||||
const param_union = linux.futex_param4{
|
||||
const param_union: linux.futex_param4 = .{
|
||||
.val2 = 0xaabbcc,
|
||||
};
|
||||
assert(@intFromPtr(param_union.timeout) == 0xaabbcc);
|
||||
}
|
||||
|
||||
test "futex v1" {
|
||||
var lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
|
||||
var lock: std.atomic.Value(u32) = .init(1);
|
||||
var rc: usize = 0;
|
||||
|
||||
// No-op wait, lock value is not expected value
|
||||
|
|
@ -230,14 +229,14 @@ test "futex v1" {
|
|||
const val3 = 1;
|
||||
const wake_nr = 3;
|
||||
const requeue_max = std.math.maxInt(u31);
|
||||
var target_lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
|
||||
var target_lock: std.atomic.Value(u32) = .init(1);
|
||||
rc = linux.futex(&lock.raw, .{ .cmd = .CMP_REQUEUE, .private = true }, wake_nr, .{ .val2 = requeue_max }, &target_lock.raw, val3);
|
||||
try expectEqual(0, rc);
|
||||
}
|
||||
|
||||
// WAKE_OP - just to see if we can construct the arguments ...
|
||||
{
|
||||
var lock2: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
|
||||
var lock2: std.atomic.Value(u32) = .init(1);
|
||||
const wake1_nr = 2;
|
||||
const wake2_nr = 3;
|
||||
const wake_op = linux.FUTEX_WAKE_OP{
|
||||
|
|
@ -276,18 +275,18 @@ test "futex v1" {
|
|||
}
|
||||
|
||||
comptime {
|
||||
assert(2 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = .U32, .private = false })));
|
||||
assert(128 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = @enumFromInt(0), .private = true })));
|
||||
std.debug.assert(2 == @as(u32, @bitCast(linux.Futex2.Wait{ .size = .U32, .private = false })));
|
||||
std.debug.assert(128 == @as(u32, @bitCast(linux.Futex2.Wait{ .size = @enumFromInt(0), .private = true })));
|
||||
}
|
||||
|
||||
test "futex2_waitv" {
|
||||
const locks = [_]std.atomic.Value(u32){
|
||||
std.atomic.Value(u32).init(1),
|
||||
std.atomic.Value(u32).init(1),
|
||||
std.atomic.Value(u32).init(1),
|
||||
const locks: [3]std.atomic.Value(u32) = .{
|
||||
.init(1),
|
||||
.init(1),
|
||||
.init(1),
|
||||
};
|
||||
|
||||
const futexes = [_]linux.futex2_waitone{
|
||||
const futexes: [3]linux.Futex2.WaitOne = .{
|
||||
.{
|
||||
.val = 1,
|
||||
.uaddr = @intFromPtr(&locks[0].raw),
|
||||
|
|
@ -305,8 +304,9 @@ test "futex2_waitv" {
|
|||
},
|
||||
};
|
||||
|
||||
const timeout = linux.kernel_timespec{ .sec = 0, .nsec = 2 }; // absolute timeout, so this is 1970...
|
||||
const rc = linux.futex2_waitv(&futexes, futexes.len, .{}, &timeout, .MONOTONIC);
|
||||
// absolute timeout, so this is 1970...
|
||||
const timeout: linux.kernel_timespec = .{ .sec = 0, .nsec = 2 };
|
||||
const rc = linux.futex2_waitv(futexes[0..], .{}, &timeout, .MONOTONIC);
|
||||
switch (linux.errno(rc)) {
|
||||
.NOSYS => return error.SkipZigTest, // futex2_waitv added in kernel v5.16
|
||||
else => |err| try expectEqual(.TIMEDOUT, err),
|
||||
|
|
@ -317,16 +317,16 @@ test "futex2_waitv" {
|
|||
// return ENOSYS.
|
||||
fn futex2_skip_if_unsupported() !void {
|
||||
const lock: u32 = 0;
|
||||
const rc = linux.futex2_wake(&lock, 0, 1, .{ .size = .U32, .private = true });
|
||||
const rc = linux.futex2_wake(&lock, .empty, 1, .{ .size = .U32, .private = true });
|
||||
if (linux.errno(rc) == .NOSYS) {
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
}
|
||||
|
||||
test "futex2_wait" {
|
||||
var lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
|
||||
var lock: std.atomic.Value(u32) = .init(1);
|
||||
var rc: usize = 0;
|
||||
const mask = 0x1;
|
||||
const mask: linux.Futex2.Bitset = .{ .waiter1 = true };
|
||||
|
||||
try futex2_skip_if_unsupported();
|
||||
|
||||
|
|
@ -343,7 +343,7 @@ test "futex2_wait" {
|
|||
try expectEqual(.INVAL, linux.errno(rc));
|
||||
}
|
||||
|
||||
const flags = linux.FUTEX2_FLAGS{ .size = .U32, .private = true };
|
||||
const flags: linux.Futex2.Wait = .{ .size = .U32, .private = true };
|
||||
// no-wait, lock state mismatch
|
||||
rc = linux.futex2_wait(&lock.raw, 2, mask, flags, null, .MONOTONIC);
|
||||
try expectEqual(.AGAIN, linux.errno(rc));
|
||||
|
|
@ -372,23 +372,23 @@ test "futex2_wait" {
|
|||
}
|
||||
|
||||
test "futex2_wake" {
|
||||
var lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
|
||||
var lock: std.atomic.Value(u32) = .init(1);
|
||||
|
||||
try futex2_skip_if_unsupported();
|
||||
|
||||
const rc = linux.futex2_wake(&lock.raw, 0xFF, 1, .{ .size = .U32, .private = true });
|
||||
const rc = linux.futex2_wake(&lock.raw, .fromInt(0xFF), 1, .{ .size = .U32, .private = true });
|
||||
try expectEqual(0, rc);
|
||||
}
|
||||
|
||||
test "futex2_requeue" {
|
||||
try futex2_skip_if_unsupported();
|
||||
|
||||
const locks = [_]std.atomic.Value(u32){
|
||||
std.atomic.Value(u32).init(1),
|
||||
std.atomic.Value(u32).init(1),
|
||||
const locks: [2]std.atomic.Value(u32) = .{
|
||||
.init(1),
|
||||
.init(1),
|
||||
};
|
||||
|
||||
const futexes = [_]linux.futex2_waitone{
|
||||
const futexes: [2]linux.Futex2.WaitOne = .{
|
||||
.{
|
||||
.val = 1,
|
||||
.uaddr = @intFromPtr(&locks[0].raw),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue