| author | |
| committer | |
| log | cfe5defd025b79ade5f9243a7d6ebaad176cb6ad |
| tree | 7f85cd11d09dac313910d64610a8ceae649360bc |
| parent | 850655f06b7dd8dc6e637d4980e6b7f9ac1b43d9 |
* Use `packed struct` for flags arguments. So, instead of
`linux.FUTEX.WAIT` use `.{ .cmd = .WAIT, .private = true }`
* rename `futex_wait` and `futex_wake` which didn't actually specify
wait/wake, as `futex_3arg` and `futex_4arg` (as its the number
of parameters that is different, the `op` is whatever is specified.
* expose the full six-arg flavor of the syscall (for some of the advanced
ops), and add packed structs for their arguments.
* Use a `packed union` to support the 4th parameter which is sometimes a
`timespec` pointer, and sometimes a `u32`.
* Add tests that make sure the structure layout is correct and that the
basic argument passing is working (no actual futexes are contended).4 files changed, 190 insertions(+), 38 deletions(-)
lib/std/Thread.zig+3-3| ... | ... | @@ -1539,10 +1539,10 @@ const LinuxThreadImpl = struct { |
| 1539 | 1539 | continue; |
| 1540 | 1540 | } |
| 1541 | 1541 | |
| 1542 | switch (linux.E.init(linux.futex_wait( | |
| 1542 | switch (linux.E.init(linux.futex_4arg( | |
| 1543 | 1543 | &self.thread.child_tid.raw, |
| 1544 | linux.FUTEX.WAIT, | |
| 1545 | tid, | |
| 1544 | .{ .cmd = .WAIT, .private = false }, | |
| 1545 | @bitCast(tid), | |
| 1546 | 1546 | null, |
| 1547 | 1547 | ))) { |
| 1548 | 1548 | .SUCCESS => continue, |
lib/std/Thread/Futex.zig+8-8| ... | ... | @@ -262,10 +262,10 @@ const LinuxImpl = struct { |
| 262 | 262 | ts.nsec = @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s)); |
| 263 | 263 | } |
| 264 | 264 | |
| 265 | const rc = linux.futex_wait( | |
| 266 | @as(*const i32, @ptrCast(&ptr.raw)), | |
| 267 | linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAIT, | |
| 268 | @as(i32, @bitCast(expect)), | |
| 265 | const rc = linux.futex_4arg( | |
| 266 | &ptr.raw, | |
| 267 | .{ .cmd = .WAIT, .private = true }, | |
| 268 | expect, | |
| 269 | 269 | if (timeout != null) &ts else null, |
| 270 | 270 | ); |
| 271 | 271 | |
| ... | ... | @@ -284,10 +284,10 @@ const LinuxImpl = struct { |
| 284 | 284 | } |
| 285 | 285 | |
| 286 | 286 | fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { |
| 287 | const rc = linux.futex_wake( | |
| 288 | @as(*const i32, @ptrCast(&ptr.raw)), | |
| 289 | linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAKE, | |
| 290 | std.math.cast(i32, max_waiters) orelse std.math.maxInt(i32), | |
| 287 | const rc = linux.futex_3arg( | |
| 288 | &ptr.raw, | |
| 289 | .{ .cmd = .WAKE, .private = true }, | |
| 290 | @min(max_waiters, std.math.maxInt(i32)), | |
| 291 | 291 | ); |
| 292 | 292 | |
| 293 | 293 | switch (linux.E.init(rc)) { |
lib/std/os/linux.zig+89-27| ... | ... | @@ -673,12 +673,34 @@ pub fn fallocate(fd: i32, mode: i32, offset: i64, length: i64) usize { |
| 673 | 673 | } |
| 674 | 674 | } |
| 675 | 675 | |
| 676 | pub fn futex_wait(uaddr: *const i32, futex_op: u32, val: i32, timeout: ?*const timespec) usize { | |
| 677 | return syscall4(.futex, @intFromPtr(uaddr), futex_op, @as(u32, @bitCast(val)), @intFromPtr(timeout)); | |
| 676 | // The 4th parameter to the v1 futex syscall can either be an optional | |
| 677 | // pointer to a timespec, or a uint32, depending on which "op" is being | |
| 678 | // performed. | |
| 679 | pub const futex_param4 = extern union { | |
| 680 | timeout: ?*const timespec, | |
| 681 | /// On all platforms only the bottom 32-bits of `val2` are relevant. | |
| 682 | /// This is 64-bit to match the pointer in the union. | |
| 683 | val2: usize, | |
| 684 | }; | |
| 685 | ||
| 686 | /// The futex v1 syscall, see also the newer the futex2_{wait,wakeup,requeue,waitv} syscalls. | |
| 687 | /// | |
| 688 | /// The futex_op parameter is a sub-command and flags. The sub-command | |
| 689 | /// defines which of the subsequent paramters are relevant. | |
| 690 | pub fn futex(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32, val2timeout: futex_param4, uaddr2: ?*const anyopaque, val3: u32) usize { | |
| 691 | return syscall6(.futex, @intFromPtr(uaddr), @as(u32, @bitCast(futex_op)), val, @intFromPtr(val2timeout.timeout), @intFromPtr(uaddr2), val3); | |
| 692 | } | |
| 693 | ||
| 694 | /// Three-argument variation of the v1 futex call. Only suitable for a | |
| 695 | /// futex_op that ignores the remaining arguments (e.g., FUTUX_OP.WAKE). | |
| 696 | pub fn futex_3arg(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32) usize { | |
| 697 | return syscall3(.futex, @intFromPtr(uaddr), @as(u32, @bitCast(futex_op)), val); | |
| 678 | 698 | } |
| 679 | 699 | |
| 680 | pub fn futex_wake(uaddr: *const i32, futex_op: u32, val: i32) usize { | |
| 681 | return syscall3(.futex, @intFromPtr(uaddr), futex_op, @as(u32, @bitCast(val))); | |
| 700 | /// Four-argument variation on the v1 futex call. Only suitable for | |
| 701 | /// futex_op that ignores the remaining arguments (e.g., FUTEX_OP.WAIT). | |
| 702 | pub fn futex_4arg(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32, timeout: ?*const timespec) usize { | |
| 703 | return syscall4(.futex, @intFromPtr(uaddr), @as(u32, @bitCast(futex_op)), val, @intFromPtr(timeout)); | |
| 682 | 704 | } |
| 683 | 705 | |
| 684 | 706 | /// Given an array of `futex_waitv`, wait on each uaddr. |
| ... | ... | @@ -3385,29 +3407,6 @@ pub const FALLOC = struct { |
| 3385 | 3407 | pub const FL_UNSHARE_RANGE = 0x40; |
| 3386 | 3408 | }; |
| 3387 | 3409 | |
| 3388 | pub const FUTEX = struct { | |
| 3389 | pub const WAIT = 0; | |
| 3390 | pub const WAKE = 1; | |
| 3391 | pub const FD = 2; | |
| 3392 | pub const REQUEUE = 3; | |
| 3393 | pub const CMP_REQUEUE = 4; | |
| 3394 | pub const WAKE_OP = 5; | |
| 3395 | pub const LOCK_PI = 6; | |
| 3396 | pub const UNLOCK_PI = 7; | |
| 3397 | pub const TRYLOCK_PI = 8; | |
| 3398 | pub const WAIT_BITSET = 9; | |
| 3399 | pub const WAKE_BITSET = 10; | |
| 3400 | pub const WAIT_REQUEUE_PI = 11; | |
| 3401 | pub const CMP_REQUEUE_PI = 12; | |
| 3402 | ||
| 3403 | pub const PRIVATE_FLAG = 128; | |
| 3404 | ||
| 3405 | pub const CLOCK_REALTIME = 256; | |
| 3406 | ||
| 3407 | /// Max numbers of elements in a `futex_waitv` array. | |
| 3408 | pub const WAITV_MAX = 128; | |
| 3409 | }; | |
| 3410 | ||
| 3411 | 3410 | pub const FUTEX2 = struct { |
| 3412 | 3411 | pub const SIZE_U8 = 0x00; |
| 3413 | 3412 | pub const SIZE_U16 = 0x01; |
| ... | ... | @@ -3418,6 +3417,69 @@ pub const FUTEX2 = struct { |
| 3418 | 3417 | pub const PRIVATE = FUTEX.PRIVATE_FLAG; |
| 3419 | 3418 | }; |
| 3420 | 3419 | |
| 3420 | // Futex v1 API commands. See futex man page for each command's | |
| 3421 | // interpretation of the futex arguments. | |
| 3422 | pub const FUTEX_COMMAND = enum(u7) { | |
| 3423 | WAIT = 0, | |
| 3424 | WAKE = 1, | |
| 3425 | FD = 2, | |
| 3426 | REQUEUE = 3, | |
| 3427 | CMP_REQUEUE = 4, | |
| 3428 | WAKE_OP = 5, | |
| 3429 | LOCK_PI = 6, | |
| 3430 | UNLOCK_PI = 7, | |
| 3431 | TRYLOCK_PI = 8, | |
| 3432 | WAIT_BITSET = 9, | |
| 3433 | WAKE_BITSET = 10, | |
| 3434 | WAIT_REQUEUE_PI = 11, | |
| 3435 | CMP_REQUEUE_PI = 12, | |
| 3436 | }; | |
| 3437 | ||
| 3438 | /// Futex v1 API command and flags for the `futex_op` parameter | |
| 3439 | pub const FUTEX_OP = packed struct(u32) { | |
| 3440 | cmd: FUTEX_COMMAND, | |
| 3441 | private: bool, | |
| 3442 | realtime: bool = false, // realtime clock vs. monotonic clock | |
| 3443 | _reserved: u23 = 0, | |
| 3444 | }; | |
| 3445 | ||
| 3446 | /// Futex v1 FUTEX_WAKE_OP `val3` operation: | |
| 3447 | pub const FUTEX_WAKE_OP = packed struct(u32) { | |
| 3448 | cmd: FUTEX_WAKE_OP_CMD, | |
| 3449 | /// From C API `FUTEX_OP_ARG_SHIFT`: Use (1 << oparg) as operand | |
| 3450 | arg_shift: bool = false, | |
| 3451 | cmp: FUTEX_WAKE_OP_CMP, | |
| 3452 | oparg: u12, | |
| 3453 | cmdarg: u12, | |
| 3454 | }; | |
| 3455 | ||
| 3456 | /// Futex v1 cmd for FUTEX_WAKE_OP `val3` command. | |
| 3457 | pub const FUTEX_WAKE_OP_CMD = enum(u3) { | |
| 3458 | /// uaddr2 = oparg | |
| 3459 | SET = 0, | |
| 3460 | /// uaddr2 += oparg | |
| 3461 | ADD = 1, | |
| 3462 | /// uaddr2 |= oparg | |
| 3463 | OR = 2, | |
| 3464 | /// uaddr2 &= ~oparg | |
| 3465 | ANDN = 3, | |
| 3466 | /// uaddr2 ^= oparg | |
| 3467 | XOR = 4, | |
| 3468 | }; | |
| 3469 | ||
| 3470 | /// Futex v1 comparison op for FUTEX_WAKE_OP `val3` cmp | |
| 3471 | pub const FUTEX_WAKE_OP_CMP = enum(u4) { | |
| 3472 | EQ = 0, | |
| 3473 | NE = 1, | |
| 3474 | LT = 2, | |
| 3475 | LE = 3, | |
| 3476 | GT = 4, | |
| 3477 | GE = 5, | |
| 3478 | }; | |
| 3479 | ||
| 3480 | /// Max numbers of elements in a `futex_waitv` array. | |
| 3481 | pub const FUTEX2_WAITV_MAX = 128; | |
| 3482 | ||
| 3421 | 3483 | pub const PROT = struct { |
| 3422 | 3484 | /// page can not be accessed |
| 3423 | 3485 | pub const NONE = 0x0; |
lib/std/os/linux/test.zig+90| ... | ... | @@ -207,6 +207,96 @@ test "sysinfo" { |
| 207 | 207 | try expect(info.mem_unit <= std.heap.page_size_max); |
| 208 | 208 | } |
| 209 | 209 | |
| 210 | comptime { | |
| 211 | std.debug.assert(128 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = true, .realtime = false }))); | |
| 212 | std.debug.assert(256 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = false, .realtime = true }))); | |
| 213 | ||
| 214 | // Check futex_param4 union is packed correctly | |
| 215 | const param_union = linux.futex_param4{ | |
| 216 | .val2 = 0xaabbcc, | |
| 217 | }; | |
| 218 | std.debug.assert(@intFromPtr(param_union.timeout) == 0xaabbcc); | |
| 219 | } | |
| 220 | ||
| 221 | test "futex v1" { | |
| 222 | var lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1); | |
| 223 | var rc: usize = 0; | |
| 224 | ||
| 225 | // No-op wait, lock value is not expected value | |
| 226 | rc = linux.futex(&lock.raw, .{ .cmd = .WAIT, .private = true }, 2, .{ .timeout = null }, null, 0); | |
| 227 | try expectEqual(.AGAIN, linux.E.init(rc)); | |
| 228 | ||
| 229 | rc = linux.futex_4arg(&lock.raw, .{ .cmd = .WAIT, .private = true }, 2, null); | |
| 230 | try expectEqual(.AGAIN, linux.E.init(rc)); | |
| 231 | ||
| 232 | // Short-fuse wait, timeout kicks in | |
| 233 | rc = linux.futex(&lock.raw, .{ .cmd = .WAIT, .private = true }, 1, .{ .timeout = &.{ .sec = 0, .nsec = 2 } }, null, 0); | |
| 234 | try expectEqual(.TIMEDOUT, linux.E.init(rc)); | |
| 235 | ||
| 236 | rc = linux.futex_4arg(&lock.raw, .{ .cmd = .WAIT, .private = true }, 1, &.{ .sec = 0, .nsec = 2 }); | |
| 237 | try expectEqual(.TIMEDOUT, linux.E.init(rc)); | |
| 238 | ||
| 239 | // Wakeup (no waiters) | |
| 240 | rc = linux.futex(&lock.raw, .{ .cmd = .WAKE, .private = true }, 2, .{ .timeout = null }, null, 0); | |
| 241 | try expectEqual(0, rc); | |
| 242 | ||
| 243 | rc = linux.futex_3arg(&lock.raw, .{ .cmd = .WAKE, .private = true }, 2); | |
| 244 | try expectEqual(0, rc); | |
| 245 | ||
| 246 | // CMP_REQUEUE - val3 mismatch | |
| 247 | rc = linux.futex(&lock.raw, .{ .cmd = .CMP_REQUEUE, .private = true }, 2, .{ .val2 = 0 }, null, 99); | |
| 248 | try expectEqual(.AGAIN, linux.E.init(rc)); | |
| 249 | ||
| 250 | // CMP_REQUEUE - requeue (but no waiters, so ... not much) | |
| 251 | { | |
| 252 | const val3 = 1; | |
| 253 | const wake_nr = 3; | |
| 254 | const requeue_max = std.math.maxInt(u31); | |
| 255 | var target_lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1); | |
| 256 | rc = linux.futex(&lock.raw, .{ .cmd = .CMP_REQUEUE, .private = true }, wake_nr, .{ .val2 = requeue_max }, &target_lock.raw, val3); | |
| 257 | try expectEqual(0, rc); | |
| 258 | } | |
| 259 | ||
| 260 | // WAKE_OP - just to see if we can construct the arguments ... | |
| 261 | { | |
| 262 | var lock2: std.atomic.Value(u32) = std.atomic.Value(u32).init(1); | |
| 263 | const wake1_nr = 2; | |
| 264 | const wake2_nr = 3; | |
| 265 | const wake_op = linux.FUTEX_WAKE_OP{ | |
| 266 | .cmd = .ANDN, | |
| 267 | .arg_shift = true, | |
| 268 | .cmp = .LT, | |
| 269 | .oparg = 4, | |
| 270 | .cmdarg = 5, | |
| 271 | }; | |
| 272 | ||
| 273 | rc = linux.futex(&lock.raw, .{ .cmd = .WAKE_OP, .private = true }, wake1_nr, .{ .val2 = wake2_nr }, &lock2.raw, @bitCast(wake_op)); | |
| 274 | try expectEqual(0, rc); | |
| 275 | } | |
| 276 | ||
| 277 | // WAIT_BITSET | |
| 278 | { | |
| 279 | // val1 return early | |
| 280 | rc = linux.futex(&lock.raw, .{ .cmd = .WAIT_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0xfff); | |
| 281 | try expectEqual(.AGAIN, linux.E.init(rc)); | |
| 282 | ||
| 283 | // timeout wait | |
| 284 | const timeout: linux.timespec = .{ .sec = 0, .nsec = 2 }; | |
| 285 | rc = linux.futex(&lock.raw, .{ .cmd = .WAIT_BITSET, .private = true }, 1, .{ .timeout = &timeout }, null, 0xfff); | |
| 286 | try expectEqual(.TIMEDOUT, linux.E.init(rc)); | |
| 287 | } | |
| 288 | ||
| 289 | // WAKE_BITSET | |
| 290 | { | |
| 291 | rc = linux.futex(&lock.raw, .{ .cmd = .WAKE_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0xfff000); | |
| 292 | try expectEqual(0, rc); | |
| 293 | ||
| 294 | // bitmask must have at least 1 bit set: | |
| 295 | rc = linux.futex(&lock.raw, .{ .cmd = .WAKE_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0); | |
| 296 | try expectEqual(.INVAL, linux.E.init(rc)); | |
| 297 | } | |
| 298 | } | |
| 299 | ||
| 210 | 300 | test { |
| 211 | 301 | _ = linux.IoUring; |
| 212 | 302 | } |