Skip to content

Commit 20f525b

Browse files
authored
opcode: add Futex{Wake,Wait,Waitv} (#272)
* opcode: add Futex{Wake,Wait,Waitv} These opcodes are available since Linux 6.7. * Use different user data for each futex test
1 parent a9693b9 commit 20f525b

File tree

7 files changed

+389
-1
lines changed

7 files changed

+389
-1
lines changed

build.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ fn main() {
1313
#include <linux/stat.h>
1414
#include <linux/openat2.h>
1515
#include <linux/io_uring.h>
16+
#include <linux/futex.h>
1617
"#;
1718

1819
#[cfg(not(feature = "overwrite"))]
@@ -38,7 +39,7 @@ fn main() {
3839
.derive_default(true)
3940
.generate_comments(true)
4041
.use_core()
41-
.allowlist_type("io_uring_.*|io_.qring_.*|__kernel_timespec|open_how")
42+
.allowlist_type("io_uring_.*|io_.qring_.*|__kernel_timespec|open_how|futex_waitv")
4243
.allowlist_var("__NR_io_uring.*|IOSQE_.*|IORING_.*|IO_URING_.*|SPLICE_F_FD_IN_FIXED")
4344
.generate()
4445
.unwrap()

io-uring-test/src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ fn test<S: squeue::EntryMarker, C: cqueue::EntryMarker>(
160160
tests::poll::test_eventfd_poll_remove_failed(&mut ring, &test)?;
161161
tests::poll::test_eventfd_poll_multi(&mut ring, &test)?;
162162

163+
// futex
164+
tests::futex::test_futex_wait(&mut ring, &test)?;
165+
tests::futex::test_futex_wake(&mut ring, &test)?;
166+
tests::futex::test_futex_waitv(&mut ring, &test)?;
167+
163168
// regression test
164169
tests::regression::test_issue154(&mut ring, &test)?;
165170

io-uring-test/src/tests/futex.rs

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
use crate::Test;
2+
use io_uring::types::FutexWaitV;
3+
use io_uring::{cqueue, opcode, squeue, IoUring};
4+
use std::sync::atomic::{AtomicU32, Ordering};
5+
use std::sync::Arc;
6+
use std::time::Duration;
7+
use std::{io, ptr, thread};
8+
9+
// Not defined by libc.
10+
//
11+
// From: https://github.com/torvalds/linux/blob/v6.7/include/uapi/linux/futex.h#L63
12+
const FUTEX2_SIZE_U32: u32 = 2;
13+
14+
const INIT_VAL: u32 = 0xDEAD_BEEF;
15+
16+
fn syscall_futex(futex: *const u32, op: libc::c_int, val: u32) -> io::Result<i64> {
17+
let ret = unsafe {
18+
libc::syscall(
19+
libc::SYS_futex,
20+
futex,
21+
op,
22+
val,
23+
ptr::null::<u8>(),
24+
ptr::null::<u8>(),
25+
0u32,
26+
)
27+
};
28+
if ret >= 0 {
29+
Ok(ret)
30+
} else {
31+
Err(io::Error::from_raw_os_error(-ret as _))
32+
}
33+
}
34+
35+
pub fn test_futex_wait<S: squeue::EntryMarker, C: cqueue::EntryMarker>(
36+
ring: &mut IoUring<S, C>,
37+
test: &Test,
38+
) -> anyhow::Result<()> {
39+
require!(
40+
test;
41+
test.probe.is_supported(opcode::FutexWait::CODE);
42+
);
43+
44+
const USER_DATA: u64 = 0xDEAD_BEEF_DEAD_BEEF;
45+
46+
println!("test futex_wait");
47+
48+
let mut futex = INIT_VAL;
49+
50+
let futex_wait_e = opcode::FutexWait::new(
51+
&futex,
52+
INIT_VAL as u64,
53+
// NB. FUTEX_BITSET_MATCH_ANY is signed. We are operating on 32-bit futex, thus this mask
54+
// must be 32-bit. Converting directly from c_int to u64 will yield `u64::MAX`, which is
55+
// invalid.
56+
libc::FUTEX_BITSET_MATCH_ANY as u32 as u64,
57+
FUTEX2_SIZE_U32,
58+
);
59+
60+
unsafe {
61+
let mut queue = ring.submission();
62+
queue
63+
.push(&futex_wait_e.build().user_data(USER_DATA).into())
64+
.expect("queue is full");
65+
}
66+
67+
ring.submit()?;
68+
thread::sleep(Duration::from_millis(100));
69+
assert_eq!(ring.completion().len(), 0);
70+
71+
futex += 1;
72+
let ret = syscall_futex(&futex, libc::FUTEX_WAKE, 1)?;
73+
assert_eq!(ret, 1);
74+
75+
ring.submit_and_wait(1)?;
76+
let cqes: Vec<cqueue::Entry> = ring.completion().map(Into::into).collect();
77+
78+
assert_eq!(cqes.len(), 1);
79+
assert_eq!(cqes[0].user_data(), USER_DATA);
80+
assert_eq!(cqes[0].result(), 0);
81+
82+
Ok(())
83+
}
84+
85+
pub fn test_futex_wake<S: squeue::EntryMarker, C: cqueue::EntryMarker>(
86+
ring: &mut IoUring<S, C>,
87+
test: &Test,
88+
) -> anyhow::Result<()> {
89+
require!(
90+
test;
91+
test.probe.is_supported(opcode::FutexWake::CODE);
92+
);
93+
94+
const USER_DATA: u64 = 0xBEEF_DEAD_BEEF_DEAD;
95+
96+
println!("test futex_wake");
97+
98+
let futex = Arc::new(AtomicU32::new(INIT_VAL));
99+
100+
let wait_thread = std::thread::spawn({
101+
let futex = futex.clone();
102+
move || {
103+
while futex.load(Ordering::Relaxed) == INIT_VAL {
104+
let ret = syscall_futex(futex.as_ptr(), libc::FUTEX_WAIT, INIT_VAL);
105+
assert_eq!(ret.unwrap(), 0);
106+
}
107+
}
108+
});
109+
110+
thread::sleep(Duration::from_millis(100));
111+
assert!(!wait_thread.is_finished());
112+
113+
futex.store(INIT_VAL + 1, Ordering::Relaxed);
114+
115+
let futex_wake_e = opcode::FutexWake::new(
116+
futex.as_ptr(),
117+
1,
118+
// NB. See comments above for why it cannot be a single `as u64`.
119+
libc::FUTEX_BITSET_MATCH_ANY as u32 as u64,
120+
FUTEX2_SIZE_U32,
121+
);
122+
unsafe {
123+
let mut queue = ring.submission();
124+
queue
125+
.push(&futex_wake_e.build().user_data(USER_DATA).into())
126+
.expect("queue is full");
127+
}
128+
ring.submit_and_wait(1)?;
129+
let cqes: Vec<cqueue::Entry> = ring.completion().map(Into::into).collect();
130+
assert_eq!(cqes.len(), 1);
131+
assert_eq!(cqes[0].user_data(), USER_DATA);
132+
assert_eq!(cqes[0].result(), 1);
133+
134+
wait_thread.join().unwrap();
135+
136+
Ok(())
137+
}
138+
139+
pub fn test_futex_waitv<S: squeue::EntryMarker, C: cqueue::EntryMarker>(
140+
ring: &mut IoUring<S, C>,
141+
test: &Test,
142+
) -> anyhow::Result<()> {
143+
require!(
144+
test;
145+
test.probe.is_supported(opcode::FutexWaitV::CODE);
146+
);
147+
148+
const USER_DATA: u64 = 0xDEAD_BEEF_BEEF_DEAD;
149+
150+
println!("test futex_waitv");
151+
152+
const FUTEX_CNT: usize = 5;
153+
const TRIGGER_IDX: usize = 3;
154+
155+
let mut futexes = [INIT_VAL; FUTEX_CNT];
156+
let mut waitv = [FutexWaitV::default(); FUTEX_CNT];
157+
for (futex, waitv) in futexes.iter().zip(&mut waitv) {
158+
*waitv = FutexWaitV::new()
159+
.val(INIT_VAL as u64)
160+
.uaddr(std::ptr::from_ref(futex) as _)
161+
.flags(FUTEX2_SIZE_U32);
162+
}
163+
164+
let futex_waitv_e = opcode::FutexWaitV::new(waitv.as_ptr().cast(), waitv.len() as _);
165+
unsafe {
166+
let mut queue = ring.submission();
167+
queue
168+
.push(&futex_waitv_e.build().user_data(USER_DATA).into())
169+
.expect("queue is full");
170+
}
171+
172+
ring.submit()?;
173+
thread::sleep(Duration::from_millis(100));
174+
assert_eq!(ring.completion().len(), 0);
175+
176+
futexes[TRIGGER_IDX] += 1;
177+
let ret = syscall_futex(&futexes[TRIGGER_IDX], libc::FUTEX_WAKE, 1)?;
178+
assert_eq!(ret, 1);
179+
180+
ring.submit_and_wait(1)?;
181+
let cqes: Vec<cqueue::Entry> = ring.completion().map(Into::into).collect();
182+
assert_eq!(cqes.len(), 1);
183+
assert_eq!(cqes[0].user_data(), USER_DATA);
184+
assert_eq!(cqes[0].result(), TRIGGER_IDX as _);
185+
186+
Ok(())
187+
}

io-uring-test/src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod cancel;
22
pub mod fs;
3+
pub mod futex;
34
pub mod net;
45
pub mod poll;
56
pub mod queue;

src/opcode.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,3 +1735,102 @@ opcode! {
17351735
Entry(sqe)
17361736
}
17371737
}
1738+
1739+
// === 6.7 ===
1740+
1741+
opcode! {
1742+
/// Wait on a futex, like but not equivalant to `futex(2)`'s `FUTEX_WAIT_BITSET`.
1743+
///
1744+
/// Wait on a futex at address `futex` and which still has the value `val` and with `futex2(2)`
1745+
/// flags of `futex_flags`. `musk` can be set to a specific bitset mask, which will be matched
1746+
/// by the waking side to decide who to wake up. To always get woken, an application may use
1747+
/// `FUTEX_BITSET_MATCH_ANY` (truncated to futex bits). `futex_flags` follows the `futex2(2)`
1748+
/// flags, not the `futex(2)` v1 interface flags. `flags` are currently unused and hence `0`
1749+
/// must be passed.
1750+
#[derive(Debug)]
1751+
pub struct FutexWait {
1752+
futex: { *const u32 },
1753+
val: { u64 },
1754+
mask: { u64 },
1755+
futex_flags: { u32 },
1756+
;;
1757+
flags: u32 = 0
1758+
}
1759+
1760+
pub const CODE = sys::IORING_OP_FUTEX_WAIT;
1761+
1762+
pub fn build(self) -> Entry {
1763+
let FutexWait { futex, val, mask, futex_flags, flags } = self;
1764+
1765+
let mut sqe = sqe_zeroed();
1766+
sqe.opcode = Self::CODE;
1767+
sqe.fd = futex_flags as _;
1768+
sqe.__bindgen_anon_2.addr = futex as usize as _;
1769+
sqe.__bindgen_anon_1.off = val;
1770+
unsafe { sqe.__bindgen_anon_6.__bindgen_anon_1.as_mut().addr3 = mask };
1771+
sqe.__bindgen_anon_3.futex_flags = flags;
1772+
Entry(sqe)
1773+
}
1774+
}
1775+
1776+
opcode! {
1777+
/// Wake up waiters on a futex, like but not equivalant to `futex(2)`'s `FUTEX_WAKE_BITSET`.
1778+
///
1779+
/// Wake any waiters on the futex indicated by `futex` and at most `val` futexes. `futex_flags`
1780+
/// indicates the `futex2(2)` modifier flags. If a given bitset for who to wake is desired,
1781+
/// then that must be set in `mask`. Use `FUTEX_BITSET_MATCH_ANY` (truncated to futex bits) to
1782+
/// match any waiter on the given futex. `flags` are currently unused and hence `0` must be
1783+
/// passed.
1784+
#[derive(Debug)]
1785+
pub struct FutexWake {
1786+
futex: { *const u32 },
1787+
val: { u64 },
1788+
mask: { u64 },
1789+
futex_flags: { u32 },
1790+
;;
1791+
flags: u32 = 0
1792+
}
1793+
1794+
pub const CODE = sys::IORING_OP_FUTEX_WAKE;
1795+
1796+
pub fn build(self) -> Entry {
1797+
let FutexWake { futex, val, mask, futex_flags, flags } = self;
1798+
1799+
let mut sqe = sqe_zeroed();
1800+
sqe.opcode = Self::CODE;
1801+
sqe.fd = futex_flags as _;
1802+
sqe.__bindgen_anon_2.addr = futex as usize as _;
1803+
sqe.__bindgen_anon_1.off = val;
1804+
unsafe { sqe.__bindgen_anon_6.__bindgen_anon_1.as_mut().addr3 = mask };
1805+
sqe.__bindgen_anon_3.futex_flags = flags;
1806+
Entry(sqe)
1807+
}
1808+
}
1809+
1810+
opcode! {
1811+
/// Wait on multiple futexes.
1812+
///
1813+
/// Wait on multiple futexes at the same time. Futexes are given by `futexv` and `nr_futex` is
1814+
/// the number of futexes in that array. Unlike `FutexWait`, the desired bitset mask and values
1815+
/// are passed in `futexv`. `flags` are currently unused and hence `0` must be passed.
1816+
#[derive(Debug)]
1817+
pub struct FutexWaitV {
1818+
futexv: { *const types::FutexWaitV },
1819+
nr_futex: { u32 },
1820+
;;
1821+
flags: u32 = 0
1822+
}
1823+
1824+
pub const CODE = sys::IORING_OP_FUTEX_WAITV;
1825+
1826+
pub fn build(self) -> Entry {
1827+
let FutexWaitV { futexv, nr_futex, flags } = self;
1828+
1829+
let mut sqe = sqe_zeroed();
1830+
sqe.opcode = Self::CODE;
1831+
sqe.__bindgen_anon_2.addr = futexv as usize as _;
1832+
sqe.len = nr_futex;
1833+
sqe.__bindgen_anon_3.futex_flags = flags;
1834+
Entry(sqe)
1835+
}
1836+
}

src/sys/sys.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2766,3 +2766,66 @@ fn bindgen_test_layout_io_uring_recvmsg_out() {
27662766
)
27672767
);
27682768
}
2769+
#[repr(C)]
2770+
#[derive(Debug, Default, Copy, Clone)]
2771+
pub struct futex_waitv {
2772+
pub val: __u64,
2773+
pub uaddr: __u64,
2774+
pub flags: __u32,
2775+
pub __reserved: __u32,
2776+
}
2777+
#[test]
2778+
fn bindgen_test_layout_futex_waitv() {
2779+
const UNINIT: ::core::mem::MaybeUninit<futex_waitv> = ::core::mem::MaybeUninit::uninit();
2780+
let ptr = UNINIT.as_ptr();
2781+
assert_eq!(
2782+
::core::mem::size_of::<futex_waitv>(),
2783+
24usize,
2784+
concat!("Size of: ", stringify!(futex_waitv))
2785+
);
2786+
assert_eq!(
2787+
::core::mem::align_of::<futex_waitv>(),
2788+
8usize,
2789+
concat!("Alignment of ", stringify!(futex_waitv))
2790+
);
2791+
assert_eq!(
2792+
unsafe { ::core::ptr::addr_of!((*ptr).val) as usize - ptr as usize },
2793+
0usize,
2794+
concat!(
2795+
"Offset of field: ",
2796+
stringify!(futex_waitv),
2797+
"::",
2798+
stringify!(val)
2799+
)
2800+
);
2801+
assert_eq!(
2802+
unsafe { ::core::ptr::addr_of!((*ptr).uaddr) as usize - ptr as usize },
2803+
8usize,
2804+
concat!(
2805+
"Offset of field: ",
2806+
stringify!(futex_waitv),
2807+
"::",
2808+
stringify!(uaddr)
2809+
)
2810+
);
2811+
assert_eq!(
2812+
unsafe { ::core::ptr::addr_of!((*ptr).flags) as usize - ptr as usize },
2813+
16usize,
2814+
concat!(
2815+
"Offset of field: ",
2816+
stringify!(futex_waitv),
2817+
"::",
2818+
stringify!(flags)
2819+
)
2820+
);
2821+
assert_eq!(
2822+
unsafe { ::core::ptr::addr_of!((*ptr).__reserved) as usize - ptr as usize },
2823+
20usize,
2824+
concat!(
2825+
"Offset of field: ",
2826+
stringify!(futex_waitv),
2827+
"::",
2828+
stringify!(__reserved)
2829+
)
2830+
);
2831+
}

0 commit comments

Comments
 (0)