Skip to content

Commit 0349fe5

Browse files
authored
Merge pull request #22 from ublk-org/single-cpu-affinity
Single cpu affinity(UBLK_DEV_F_SINGLE_CPU_AFFINITY)
2 parents 80be347 + 99b5c7b commit 0349fe5

File tree

3 files changed

+242
-5
lines changed

3 files changed

+242
-5
lines changed

src/ctrl.rs

Lines changed: 133 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,48 @@ impl UblkQueueAffinity {
5050
pub fn addr(&self) -> *const u8 {
5151
self.affinity.as_bytes().as_ptr()
5252
}
53+
5354
pub fn to_bits_vec(&self) -> Vec<usize> {
5455
self.affinity.into_iter().collect()
5556
}
57+
58+
/// Get a random CPU from the affinity set
59+
fn get_random_cpu(&self) -> Option<usize> {
60+
let cpus: Vec<usize> = self.affinity.into_iter().collect();
61+
if cpus.is_empty() {
62+
return None;
63+
}
64+
65+
// Simple pseudo-random selection using current time and thread ID
66+
let mut seed = std::time::SystemTime::now()
67+
.duration_since(std::time::UNIX_EPOCH)
68+
.unwrap_or_default()
69+
.as_nanos() as usize;
70+
71+
unsafe {
72+
seed = seed.wrapping_add(libc::gettid() as usize);
73+
}
74+
75+
Some(cpus[seed % cpus.len()])
76+
}
77+
78+
/// Create a new affinity with only the specified CPU
79+
pub fn from_single_cpu(cpu: usize) -> UblkQueueAffinity {
80+
let mut affinity = UblkQueueAffinity::new();
81+
affinity.affinity.set(cpu, true);
82+
affinity
83+
}
84+
85+
/// Set a specific CPU in the affinity
86+
pub fn set_cpu(&mut self, cpu: usize) {
87+
self.affinity.set(cpu, true);
88+
}
89+
90+
/// Clear all CPUs and set only the specified one
91+
pub fn set_only_cpu(&mut self, cpu: usize) {
92+
self.affinity = Bitmap::new();
93+
self.affinity.set(cpu, true);
94+
}
5695
}
5796

5897
#[repr(C)]
@@ -265,6 +304,7 @@ struct UblkCtrlInner {
265304
dev_flags: UblkFlags,
266305
cmd_token: i32,
267306
queue_tids: Vec<i32>,
307+
queue_selected_cpus: Vec<usize>,
268308
nr_queues_configured: u16,
269309
}
270310

@@ -322,6 +362,13 @@ impl UblkCtrlInner {
322362
}
323363
tids
324364
},
365+
queue_selected_cpus: {
366+
let mut cpus = Vec::<usize>::with_capacity(nr_queues as usize);
367+
unsafe {
368+
cpus.set_len(nr_queues as usize);
369+
}
370+
cpus
371+
},
325372
nr_queues_configured: 0,
326373
dev_flags,
327374
features: None,
@@ -898,8 +945,16 @@ impl UblkCtrlInner {
898945
let mut map: serde_json::Map<String, serde_json::Value> = serde_json::Map::new();
899946

900947
for qid in 0..dev.dev_info.nr_hw_queues {
901-
let mut affinity = self::UblkQueueAffinity::new();
902-
self.get_queue_affinity(qid as u32, &mut affinity)?;
948+
let affinity = if self.dev_flags.contains(UblkFlags::UBLK_DEV_F_SINGLE_CPU_AFFINITY) {
949+
// Use the stored single CPU affinity
950+
let selected_cpu = self.queue_selected_cpus[qid as usize];
951+
UblkQueueAffinity::from_single_cpu(selected_cpu)
952+
} else {
953+
// Use the original full affinity from the kernel
954+
let mut full_affinity = UblkQueueAffinity::new();
955+
self.get_queue_affinity(qid as u32, &mut full_affinity)?;
956+
full_affinity
957+
};
903958

904959
map.insert(
905960
format!("{}", qid),
@@ -1410,6 +1465,23 @@ impl UblkCtrl {
14101465

14111466
let mut affinity = UblkQueueAffinity::new();
14121467
self.get_queue_affinity(q as u32, &mut affinity).unwrap();
1468+
1469+
let (final_affinity, selected_cpu) = if self.get_dev_flags().contains(UblkFlags::UBLK_DEV_F_SINGLE_CPU_AFFINITY) {
1470+
// Select a random CPU from the affinity and create single-CPU affinity
1471+
let selected_cpu = affinity.get_random_cpu().unwrap_or(0);
1472+
let single_cpu_affinity = UblkQueueAffinity::from_single_cpu(selected_cpu);
1473+
(single_cpu_affinity, Some(selected_cpu))
1474+
} else {
1475+
// Use original full affinity
1476+
(affinity, None)
1477+
};
1478+
1479+
// Store the selected CPU for later use in build_json (if single CPU mode is enabled)
1480+
if let Some(cpu) = selected_cpu {
1481+
let mut inner = self.get_inner_mut();
1482+
inner.queue_selected_cpus[q as usize] = cpu;
1483+
}
1484+
14131485
let mut _q_fn = q_fn.clone();
14141486

14151487
q_threads.push(std::thread::spawn(move || {
@@ -1418,8 +1490,8 @@ impl UblkCtrl {
14181490
unsafe {
14191491
libc::pthread_setaffinity_np(
14201492
libc::pthread_self(),
1421-
affinity.buf_len(),
1422-
affinity.addr() as *const libc::cpu_set_t,
1493+
final_affinity.buf_len(),
1494+
final_affinity.addr() as *const libc::cpu_set_t,
14231495
);
14241496
}
14251497
_tx.send((q, unsafe { libc::gettid() })).unwrap();
@@ -1510,7 +1582,7 @@ impl UblkCtrl {
15101582

15111583
#[cfg(test)]
15121584
mod tests {
1513-
use crate::ctrl::UblkCtrlBuilder;
1585+
use crate::ctrl::{UblkCtrlBuilder, UblkQueueAffinity};
15141586
use crate::io::{UblkDev, UblkIOCtx, UblkQueue};
15151587
use crate::UblkError;
15161588
use crate::{ctrl::UblkCtrl, UblkFlags, UblkIORes};
@@ -1718,4 +1790,60 @@ mod tests {
17181790

17191791
handle.join().unwrap();
17201792
}
1793+
1794+
/// Test UBLK_DEV_F_SINGLE_CPU_AFFINITY feature
1795+
#[test]
1796+
fn test_single_cpu_affinity() {
1797+
// Test 1: Verify the flag is properly defined and can be used
1798+
let single_cpu_flags = UblkFlags::UBLK_DEV_F_ADD_DEV | UblkFlags::UBLK_DEV_F_SINGLE_CPU_AFFINITY;
1799+
let normal_flags = UblkFlags::UBLK_DEV_F_ADD_DEV;
1800+
1801+
assert!(single_cpu_flags.contains(UblkFlags::UBLK_DEV_F_SINGLE_CPU_AFFINITY));
1802+
assert!(!normal_flags.contains(UblkFlags::UBLK_DEV_F_SINGLE_CPU_AFFINITY));
1803+
1804+
// Test 2: Create control devices with and without the flag
1805+
let ctrl_with_flag = UblkCtrlBuilder::default()
1806+
.name("test_single_cpu")
1807+
.depth(16_u16)
1808+
.nr_queues(2_u16)
1809+
.dev_flags(single_cpu_flags)
1810+
.build()
1811+
.unwrap();
1812+
1813+
let ctrl_without_flag = UblkCtrlBuilder::default()
1814+
.name("test_normal")
1815+
.depth(16_u16)
1816+
.nr_queues(2_u16)
1817+
.dev_flags(normal_flags)
1818+
.build()
1819+
.unwrap();
1820+
1821+
// Test 3: Verify flag is stored correctly in the control device
1822+
assert!(ctrl_with_flag.get_dev_flags().contains(UblkFlags::UBLK_DEV_F_SINGLE_CPU_AFFINITY));
1823+
assert!(!ctrl_without_flag.get_dev_flags().contains(UblkFlags::UBLK_DEV_F_SINGLE_CPU_AFFINITY));
1824+
1825+
// Test 4: Test UblkQueueAffinity helper methods
1826+
let test_affinity = UblkQueueAffinity::from_single_cpu(3);
1827+
let bits = test_affinity.to_bits_vec();
1828+
assert_eq!(bits.len(), 1, "Single CPU affinity should contain exactly one CPU");
1829+
assert_eq!(bits[0], 3, "Single CPU affinity should contain CPU 3");
1830+
1831+
// Test 5: Test random CPU selection (create an affinity with multiple CPUs and verify selection)
1832+
let mut multi_cpu_affinity = UblkQueueAffinity::new();
1833+
multi_cpu_affinity.set_cpu(1);
1834+
multi_cpu_affinity.set_cpu(3);
1835+
multi_cpu_affinity.set_cpu(5);
1836+
1837+
let selected_cpu = multi_cpu_affinity.get_random_cpu();
1838+
assert!(selected_cpu.is_some(), "Should be able to select a CPU from multi-CPU affinity");
1839+
1840+
let cpu = selected_cpu.unwrap();
1841+
assert!(cpu == 1 || cpu == 3 || cpu == 5, "Selected CPU should be one of the available CPUs (1, 3, or 5), got {}", cpu);
1842+
1843+
println!("✓ Single CPU affinity feature tests passed");
1844+
println!(" - Flag definition and usage: PASS");
1845+
println!(" - Control device flag storage: PASS");
1846+
println!(" - Single CPU affinity creation: PASS");
1847+
println!(" - Random CPU selection: PASS (selected CPU {})", cpu);
1848+
}
17211849
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ bitflags! {
3131
/// tell UblkCtrl that we are deleted in async
3232
const UBLK_DEV_F_DEL_DEV_ASYNC = 0b00001000;
3333

34+
/// enable single CPU affinity optimization: select one random CPU
35+
/// from queue's affinity instead of setting all CPUs
36+
const UBLK_DEV_F_SINGLE_CPU_AFFINITY = 0b00010000;
37+
3438
const UBLK_DEV_F_INTERNAL_0 = 1_u32 << 31;
3539
const UBLK_DEV_F_INTERNAL_1 = 1_u32 << 30;
3640
}

tests/basic.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,4 +592,109 @@ mod integration {
592592
ublk_state_wait_until(&ctrl, sys::UBLK_S_DEV_LIVE as u16, 20000);
593593
ctrl.del_dev().unwrap();
594594
}
595+
596+
/// Test UBLK_DEV_F_SINGLE_CPU_AFFINITY integration
597+
#[test]
598+
fn test_ublk_single_cpu_affinity() {
599+
fn verify_single_cpu_affinity(ctrl: &UblkCtrl, dev_flags: UblkFlags) {
600+
// Verify the device was created with the expected flags
601+
let tgt_flags = ctrl.get_target_flags_from_json().unwrap();
602+
assert!(UblkFlags::from_bits(tgt_flags).unwrap() == dev_flags);
603+
604+
// Read the JSON file to check queue affinities
605+
let run_path = ctrl.run_path();
606+
let json_path = Path::new(&run_path);
607+
assert!(json_path.exists() == true, "JSON file should exist");
608+
609+
let json_content = std::fs::read_to_string(json_path)
610+
.expect("Should be able to read JSON file");
611+
let json: serde_json::Value = serde_json::from_str(&json_content)
612+
.expect("Should be able to parse JSON");
613+
614+
// Check that queues section exists
615+
let queues = json.get("queues")
616+
.expect("JSON should have queues section");
617+
618+
// Verify each queue has exactly one CPU in its affinity
619+
for qid in 0..2u16 {
620+
let queue_info = queues.get(qid.to_string())
621+
.expect(&format!("Queue {} should exist in JSON", qid));
622+
623+
let affinity = queue_info.get("affinity")
624+
.expect(&format!("Queue {} should have affinity field", qid));
625+
626+
let affinity_array = affinity.as_array()
627+
.expect(&format!("Queue {} affinity should be an array", qid));
628+
629+
assert_eq!(
630+
affinity_array.len(), 1,
631+
"Queue {} should have exactly 1 CPU in affinity when UBLK_DEV_F_SINGLE_CPU_AFFINITY is set, got {}",
632+
qid, affinity_array.len()
633+
);
634+
635+
let cpu_id = affinity_array[0].as_u64()
636+
.expect(&format!("Queue {} affinity should contain valid CPU ID", qid));
637+
638+
println!("Queue {} is bound to CPU {}", qid, cpu_id);
639+
}
640+
641+
println!("✓ Single CPU affinity verification passed - each queue bound to exactly one CPU");
642+
}
643+
644+
fn single_cpu_null_handle_queue(qid: u16, dev: &UblkDev) {
645+
let bufs_rc = Rc::new(dev.alloc_queue_io_bufs());
646+
let user_copy = (dev.dev_info.flags & libublk::sys::UBLK_F_USER_COPY as u64) != 0;
647+
let bufs = bufs_rc.clone();
648+
649+
let io_handler = move |q: &UblkQueue, tag: u16, _io: &UblkIOCtx| {
650+
let iod = q.get_iod(tag);
651+
let bytes = (iod.nr_sectors << 9) as i32;
652+
653+
let buf_addr = if user_copy {
654+
std::ptr::null_mut()
655+
} else {
656+
bufs[tag as usize].as_mut_ptr()
657+
};
658+
q.complete_io_cmd(tag, buf_addr, Ok(UblkIORes::Result(bytes)));
659+
};
660+
661+
UblkQueue::new(qid, dev)
662+
.unwrap()
663+
.submit_fetch_commands(if user_copy { None } else { Some(&bufs_rc) })
664+
.wait_and_handle_io(io_handler);
665+
}
666+
667+
let dev_flags = UblkFlags::UBLK_DEV_F_ADD_DEV | UblkFlags::UBLK_DEV_F_SINGLE_CPU_AFFINITY;
668+
669+
let ctrl = UblkCtrlBuilder::default()
670+
.name("single_cpu_null")
671+
.nr_queues(2)
672+
.dev_flags(dev_flags)
673+
.ctrl_flags(libublk::sys::UBLK_F_USER_COPY.into())
674+
.build()
675+
.unwrap();
676+
677+
let tgt_init = |dev: &mut UblkDev| {
678+
dev.set_default_params(250_u64 << 30);
679+
Ok(())
680+
};
681+
682+
let q_fn = move |qid: u16, dev: &UblkDev| {
683+
single_cpu_null_handle_queue(qid, dev);
684+
};
685+
686+
ctrl.run_target(tgt_init, q_fn, move |ctrl: &UblkCtrl| {
687+
// Run basic sanity tests
688+
run_ublk_disk_sanity_test(ctrl, dev_flags);
689+
690+
// Verify single CPU affinity behavior
691+
verify_single_cpu_affinity(ctrl, dev_flags);
692+
693+
// Test that the device works normally
694+
read_ublk_disk(ctrl, true);
695+
696+
ctrl.kill_dev().unwrap();
697+
})
698+
.unwrap();
699+
}
595700
}

0 commit comments

Comments
 (0)