1#![allow(unsafe_code)]
14
15use crate::error::{LinuxError, Result, UmemError};
16use std::sync::atomic::{AtomicU64, Ordering};
17
18#[derive(Debug, Clone)]
20pub struct UmemConfig {
21 pub size: usize,
23 pub hugepage: bool,
25 pub locked: bool,
27 pub shared: bool,
29}
30
31impl Default for UmemConfig {
32 fn default() -> Self {
33 Self {
34 size: 0,
35 hugepage: false,
36 locked: true,
37 shared: false,
38 }
39 }
40}
41
42#[derive(Debug, Clone)]
44pub struct UmemRegion {
45 pub addr: *mut u8,
47 pub size: usize,
49 pub hugepage: bool,
51 pub locked: bool,
53 pub page_offset: u64,
55}
56
57unsafe impl Send for UmemRegion {}
67unsafe impl Sync for UmemRegion {}
68
69pub struct UmemManager {
74 region: Option<UmemRegion>,
76 config: UmemConfig,
78 ref_count: AtomicU64,
80 initialized: bool,
82}
83
84impl std::fmt::Debug for UmemManager {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 f.debug_struct("UmemManager")
87 .field("config", &self.config)
88 .field("initialized", &self.initialized)
89 .field("ref_count", &self.ref_count)
90 .finish()
91 }
92}
93
94impl UmemManager {
95 pub fn new(config: UmemConfig) -> Result<Self> {
103 if config.size == 0 {
104 return Err(LinuxError::Umem(UmemError::InsufficientSize {
105 actual: 0,
106 required: 4096,
107 }));
108 }
109
110 let page_size = crate::page_size();
112 if !config.size.is_multiple_of(page_size) {
113 return Err(LinuxError::Umem(UmemError::NotAligned {
114 actual: config.size,
115 expected: page_size,
116 }));
117 }
118
119 Ok(Self {
120 region: None,
121 config,
122 ref_count: AtomicU64::new(0),
123 initialized: false,
124 })
125 }
126
127 pub fn create(&mut self) -> Result<()> {
132 if self.initialized {
133 return Err(LinuxError::Umem(UmemError::AlreadyCreated));
134 }
135
136 let page_size = crate::page_size();
139
140 let mut flags = libc::MAP_PRIVATE | libc::MAP_ANONYMOUS;
142 if self.config.shared {
143 flags = libc::MAP_SHARED | libc::MAP_ANONYMOUS;
144 }
145
146 let region_size = if self.config.hugepage {
148 let hugepage_size = 2 * 1024 * 1024;
150 self.config
153 .size
154 .checked_add(hugepage_size - 1)
155 .map(|v| v & !(hugepage_size - 1))
156 .ok_or_else(|| {
157 LinuxError::InsufficientResources(
158 "UMEM size 对齐 HugePage 时溢出".to_string(),
159 )
160 })?
161 } else {
162 self.config.size
163 };
164
165 if self.config.hugepage {
166 flags |= libc::MAP_HUGETLB;
167 }
168
169 let addr = unsafe {
178 libc::mmap(
179 std::ptr::null_mut(),
180 region_size,
181 libc::PROT_READ | libc::PROT_WRITE,
182 flags,
183 -1,
184 0,
185 )
186 };
187
188 if addr == libc::MAP_FAILED {
189 let err = std::io::Error::last_os_error();
190 return Err(LinuxError::Umem(UmemError::MmapFailed(format!(
191 "mmap failed: {}",
192 err
193 ))));
194 }
195
196 let addr = addr as *mut u8;
197
198 if self.config.locked {
200 let lock_result = unsafe { libc::mlock(addr as *const libc::c_void, region_size) };
203 if lock_result != 0 {
204 let err = std::io::Error::last_os_error();
205 unsafe {
208 libc::munmap(addr as *mut libc::c_void, region_size);
209 }
210 return Err(LinuxError::Umem(UmemError::LockFailed(format!(
211 "mlock failed: {}",
212 err
213 ))));
214 }
215 }
216
217 if self.config.hugepage {
221 let madvise_ret = unsafe {
222 libc::madvise(addr as *mut libc::c_void, region_size, libc::MADV_HUGEPAGE)
223 };
224 if madvise_ret != 0 {
225 tracing::debug!("madvise(MADV_HUGEPAGE) 失败: {}", std::io::Error::last_os_error());
226 }
227 }
228
229 let page_offset = (addr as usize / page_size) as u64;
231
232 self.region = Some(UmemRegion {
233 addr,
234 size: region_size,
235 hugepage: self.config.hugepage,
236 locked: self.config.locked,
237 page_offset,
238 });
239
240 self.initialized = true;
241 self.ref_count.store(1, Ordering::SeqCst);
242
243 Ok(())
244 }
245
246 pub fn region(&self) -> Option<&UmemRegion> {
251 self.region.as_ref()
252 }
253
254 pub fn as_ptr(&self) -> *mut u8 {
259 self.region.as_ref().map_or(std::ptr::null_mut(), |r| r.addr)
260 }
261
262 #[inline]
271 pub fn slice(&self, offset: usize, len: usize) -> Option<&[u8]> {
272 let end = offset.checked_add(len)?;
273 if end > self.config.size {
274 return None;
275 }
276 let addr = self.as_ptr();
277 if addr.is_null() {
278 return None;
279 }
280 Some(unsafe { std::slice::from_raw_parts(addr.cast_const().add(offset), len) })
285 }
286
287 #[allow(clippy::mut_from_ref)]
302 #[inline]
303 pub fn slice_mut(&self, offset: usize, len: usize) -> Option<&mut [u8]> {
304 let end = offset.checked_add(len)?;
305 if end > self.config.size {
306 return None;
307 }
308 let addr = self.as_ptr();
309 if addr.is_null() {
310 return None;
311 }
312 Some(unsafe { std::slice::from_raw_parts_mut(addr.add(offset), len) })
316 }
317
318 pub fn page_offset(&self) -> u64 {
323 self.region.as_ref().map_or(0, |r| r.page_offset)
324 }
325
326 pub fn size(&self) -> usize {
331 self.config.size
332 }
333
334 pub fn is_initialized(&self) -> bool {
339 self.initialized
340 }
341
342 pub fn incref(&self) -> Result<u64> {
355 if !self.initialized {
357 return Err(LinuxError::Umem(UmemError::NotCreated));
358 }
359 loop {
365 let current = self.ref_count.load(Ordering::Acquire);
366 if current == 0 {
367 return Err(LinuxError::Umem(UmemError::MunmapFailed(
368 "incref: region already munmapped (ref_count == 0, use-after-free prevented)"
369 .to_string(),
370 )));
371 }
372 match self.ref_count.compare_exchange(
373 current,
374 current + 1,
375 Ordering::SeqCst,
376 Ordering::Acquire,
377 ) {
378 Ok(_) => return Ok(current + 1),
379 Err(_) => continue,
380 }
381 }
382 }
383
384 pub fn decref(&self) -> u64 {
390 self.ref_count.fetch_sub(1, Ordering::SeqCst) - 1
392 }
393}
394
395impl Drop for UmemManager {
396 fn drop(&mut self) {
397 if let Some(region) = &self.region {
398 let cas_result = self.ref_count.compare_exchange(
421 1,
422 0,
423 Ordering::AcqRel,
424 Ordering::Acquire,
425 );
426 if cas_result.is_ok() {
427 unsafe {
433 libc::munmap(region.addr as *mut libc::c_void, region.size);
434 }
435 }
436 }
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443
444 #[test]
445 fn test_umem_config_default() {
446 let config = UmemConfig::default();
447 assert_eq!(config.size, 0);
448 assert!(!config.hugepage);
449 assert!(config.locked);
450 assert!(!config.shared);
451 }
452
453 #[test]
454 fn test_umem_manager_creation() {
455 let config = UmemConfig {
456 size: 0,
457 ..Default::default()
458 };
459 let result = UmemManager::new(config);
460 assert!(result.is_err());
461 }
462
463 #[test]
464 fn test_umem_manager_create() {
465 let config = UmemConfig {
466 size: 4096 * 256, hugepage: false,
468 locked: false, shared: false,
470 };
471 let mut manager = UmemManager::new(config).unwrap();
472 assert!(!manager.is_initialized());
473
474 let result = manager.create();
475 assert!(result.is_ok());
476 assert!(manager.is_initialized());
477 assert!(!manager.as_ptr().is_null());
478 assert_eq!(manager.size(), 4096 * 256);
479 }
480
481 #[test]
482 fn test_umem_alignment_check() {
483 let config = UmemConfig {
484 size: 4096 + 1, ..Default::default()
486 };
487 let result = UmemManager::new(config);
488 assert!(result.is_err());
489 }
490
491 #[test]
492 fn test_umem_double_create() {
493 let config = UmemConfig {
494 size: 4096 * 64,
495 hugepage: false,
496 locked: false,
497 shared: false,
498 };
499 let mut manager = UmemManager::new(config).unwrap();
500 manager.create().unwrap();
501 let result = manager.create();
502 assert!(result.is_err());
503 }
504
505 #[test]
506 fn test_umem_size_zero_rejected() {
507 let config = UmemConfig {
508 size: 0,
509 ..Default::default()
510 };
511 let result = UmemManager::new(config);
512 assert!(result.is_err());
513 }
514
515 #[test]
516 fn test_umem_unaligned_size_rejected() {
517 let page_size = crate::page_size();
518 let config = UmemConfig {
519 size: page_size + 1,
520 ..Default::default()
521 };
522 let result = UmemManager::new(config);
523 assert!(result.is_err());
524 }
525
526 #[test]
527 fn test_umem_aligned_size_accepted() {
528 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
529 for multiplier in [1, 2, 4, 8, 16, 32, 64, 128, 256] {
530 let config = UmemConfig {
531 size: page_size * multiplier,
532 hugepage: false,
533 locked: false,
534 shared: false,
535 };
536 let result = UmemManager::new(config);
537 assert!(
538 result.is_ok(),
539 "Size {} ({} * {}) should be valid",
540 page_size * multiplier,
541 page_size,
542 multiplier
543 );
544 }
545 }
546
547 #[test]
548 fn test_umem_region_access_after_create() {
549 let config = UmemConfig {
550 size: 4096 * 128,
551 hugepage: false,
552 locked: false,
553 shared: false,
554 };
555 let mut manager = UmemManager::new(config).unwrap();
556 assert!(manager.region().is_none());
557 assert!(manager.as_ptr().is_null());
558 assert_eq!(manager.page_offset(), 0);
559
560 manager.create().unwrap();
561 assert!(manager.region().is_some());
562 assert!(!manager.as_ptr().is_null());
563
564 let region = manager.region().unwrap();
565 assert!(!region.addr.is_null());
566 assert!(region.size >= 4096 * 128);
567 assert!(!region.hugepage);
568 assert!(!region.locked);
569 }
570
571 #[test]
572 fn test_umem_size_method_returns_config_size() {
573 let config = UmemConfig {
574 size: 4096 * 64,
575 hugepage: false,
576 locked: false,
577 shared: false,
578 };
579 let manager = UmemManager::new(config.clone()).unwrap();
580 assert_eq!(manager.size(), config.size);
581 }
582
583 #[test]
584 fn test_umem_ref_counting() {
585 let config = UmemConfig {
586 size: 4096 * 64,
587 hugepage: false,
588 locked: false,
589 shared: false,
590 };
591 let mut manager = UmemManager::new(config).unwrap();
592 manager.create().unwrap();
593
594 let ref1 = manager.incref().unwrap();
595 assert_eq!(ref1, 2);
596
597 let ref2 = manager.incref().unwrap();
598 assert_eq!(ref2, 3);
599
600 let dec1 = manager.decref();
601 assert_eq!(dec1, 2);
602
603 let dec2 = manager.decref();
604 assert_eq!(dec2, 1);
605 }
606
607 #[test]
608 fn test_umem_incref_before_create_fails() {
609 let config = UmemConfig {
611 size: 4096 * 16,
612 hugepage: false,
613 locked: false,
614 shared: false,
615 };
616 let manager = UmemManager::new(config).unwrap();
617 let result = manager.incref();
618 assert!(result.is_err());
619 assert!(matches!(
620 result.unwrap_err(),
621 LinuxError::Umem(UmemError::NotCreated)
622 ));
623 }
624
625 #[test]
626 fn test_umem_hugepage_align_overflow_fails() {
627 let config = UmemConfig {
630 size: usize::MAX - 4095,
631 hugepage: true,
632 locked: false,
633 shared: false,
634 };
635 let mut manager = UmemManager::new(config).unwrap();
636 let result = manager.create();
637 assert!(result.is_err(), "HugePage 对齐溢出必须 Fail-Closed");
638 }
639
640 #[test]
641 fn test_umem_config_clone() {
642 let config = UmemConfig {
643 size: 4096 * 32,
644 hugepage: true,
645 locked: true,
646 shared: true,
647 };
648 let cloned = config.clone();
649 assert_eq!(cloned.size, config.size);
650 assert_eq!(cloned.hugepage, config.hugepage);
651 assert_eq!(cloned.locked, config.locked);
652 assert_eq!(cloned.shared, config.shared);
653 }
654
655 #[test]
656 fn test_umem_manager_debug_format() {
657 let config = UmemConfig {
658 size: 4096 * 16,
659 hugepage: false,
660 locked: false,
661 shared: false,
662 };
663 let manager = UmemManager::new(config).unwrap();
664 let debug = format!("{:?}", manager);
665 assert!(debug.contains("UmemManager"));
666 assert!(debug.contains("initialized"));
667 assert!(debug.contains("ref_count"));
668 }
669
670 #[test]
671 fn test_umem_initialized_state() {
672 let config = UmemConfig {
673 size: 4096 * 32,
674 hugepage: false,
675 locked: false,
676 shared: false,
677 };
678 let mut manager = UmemManager::new(config).unwrap();
679 assert!(!manager.is_initialized());
680
681 manager.create().unwrap();
682 assert!(manager.is_initialized());
683 }
684
685 #[test]
686 fn test_umem_large_allocation() {
687 let config = UmemConfig {
688 size: 4096 * 1024, hugepage: false,
690 locked: false,
691 shared: false,
692 };
693 let mut manager = match UmemManager::new(config) {
694 Ok(m) => m,
695 Err(_) => return, };
697
698 let result = manager.create();
699 if result.is_ok() {
700 assert!(manager.is_initialized());
701 assert!(!manager.as_ptr().is_null());
702 }
703 }
704
705 #[test]
706 fn test_umem_shared_config_flag() {
707 let config = UmemConfig {
708 size: 4096 * 32,
709 hugepage: false,
710 locked: false,
711 shared: true,
712 };
713 let mut manager = UmemManager::new(config).unwrap();
714 let result = manager.create();
715 assert!(result.is_ok());
716 assert!(manager.is_initialized());
717 }
718
719 #[test]
720 fn test_umem_region_send_sync() {
721 fn assert_send_sync<T: Send + Sync>() {}
722 assert_send_sync::<UmemRegion>();
723 }
724}