dbs_address_space/
region.rs

1// Copyright (C) 2021 Alibaba Cloud. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::ffi::CString;
5use std::fs::{File, OpenOptions};
6use std::os::unix::io::FromRawFd;
7use std::path::Path;
8use std::str::FromStr;
9
10use nix::sys::memfd;
11use vm_memory::{Address, FileOffset, GuestAddress, GuestUsize};
12
13use crate::memory::MemorySourceType;
14use crate::memory::MemorySourceType::MemFdShared;
15use crate::AddressSpaceError;
16
17/// Type of address space regions.
18///
19/// On physical machines, physical memory may have different properties, such as
20/// volatile vs non-volatile, read-only vs read-write, non-executable vs executable etc.
21/// On virtual machines, the concept of memory property may be extended to support better
22/// cooperation between the hypervisor and the guest kernel. Here address space region type means
23/// what the region will be used for by the guest OS, and different permissions and policies may
24/// be applied to different address space regions.
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum AddressSpaceRegionType {
27    /// Normal memory accessible by CPUs and IO devices.
28    DefaultMemory,
29    /// MMIO address region for Devices.
30    DeviceMemory,
31    /// DAX address region for virtio-fs/virtio-pmem.
32    DAXMemory,
33}
34
35/// Struct to maintain configuration information about a guest address region.
36#[derive(Debug, Clone)]
37pub struct AddressSpaceRegion {
38    /// Type of address space regions.
39    pub ty: AddressSpaceRegionType,
40    /// Base address of the region in virtual machine's physical address space.
41    pub base: GuestAddress,
42    /// Size of the address space region.
43    pub size: GuestUsize,
44    /// Host NUMA node ids assigned to this region.
45    pub host_numa_node_id: Option<u32>,
46
47    /// File/offset tuple to back the memory allocation.
48    file_offset: Option<FileOffset>,
49    /// Mmap permission flags.
50    perm_flags: i32,
51    /// Mmap protection flags.
52    prot_flags: i32,
53    /// Hugepage madvise hint.
54    ///
55    /// It needs 'advise' or 'always' policy in host shmem config.
56    is_hugepage: bool,
57    /// Hotplug hint.
58    is_hotplug: bool,
59    /// Anonymous memory hint.
60    ///
61    /// It should be true for regions with the MADV_DONTFORK flag enabled.
62    is_anon: bool,
63}
64
65#[allow(clippy::too_many_arguments)]
66impl AddressSpaceRegion {
67    /// Create an address space region with default configuration.
68    pub fn new(ty: AddressSpaceRegionType, base: GuestAddress, size: GuestUsize) -> Self {
69        AddressSpaceRegion {
70            ty,
71            base,
72            size,
73            host_numa_node_id: None,
74            file_offset: None,
75            perm_flags: libc::MAP_SHARED,
76            prot_flags: libc::PROT_READ | libc::PROT_WRITE,
77            is_hugepage: false,
78            is_hotplug: false,
79            is_anon: false,
80        }
81    }
82
83    /// Create an address space region with all configurable information.
84    ///
85    /// # Arguments
86    /// * `ty` - Type of the address region
87    /// * `base` - Base address in VM to map content
88    /// * `size` - Length of content to map
89    /// * `numa_node_id` - Optional NUMA node id to allocate memory from
90    /// * `file_offset` - Optional file descriptor and offset to map content from
91    /// * `perm_flags` - mmap permission flags
92    /// * `prot_flags` - mmap protection flags
93    /// * `is_hotplug` - Whether it's a region for hotplug.
94    pub fn build(
95        ty: AddressSpaceRegionType,
96        base: GuestAddress,
97        size: GuestUsize,
98        host_numa_node_id: Option<u32>,
99        file_offset: Option<FileOffset>,
100        perm_flags: i32,
101        prot_flags: i32,
102        is_hotplug: bool,
103    ) -> Self {
104        let mut region = Self::new(ty, base, size);
105
106        region.set_host_numa_node_id(host_numa_node_id);
107        region.set_file_offset(file_offset);
108        region.set_perm_flags(perm_flags);
109        region.set_prot_flags(prot_flags);
110        if is_hotplug {
111            region.set_hotplug();
112        }
113
114        region
115    }
116
117    /// Create an address space region to map memory into the virtual machine.
118    ///
119    /// # Arguments
120    /// * `base` - Base address in VM to map content
121    /// * `size` - Length of content to map
122    /// * `numa_node_id` - Optional NUMA node id to allocate memory from
123    /// * `mem_type` - Memory mapping from, 'shmem' or 'hugetlbfs'
124    /// * `mem_file_path` - Memory file path
125    /// * `mem_prealloc` - Whether to enable pre-allocation of guest memory
126    /// * `is_hotplug` - Whether it's a region for hotplug.
127    pub fn create_default_memory_region(
128        base: GuestAddress,
129        size: GuestUsize,
130        numa_node_id: Option<u32>,
131        mem_type: &str,
132        mem_file_path: &str,
133        mem_prealloc: bool,
134        is_hotplug: bool,
135    ) -> Result<AddressSpaceRegion, AddressSpaceError> {
136        Self::create_memory_region(
137            base,
138            size,
139            numa_node_id,
140            mem_type,
141            mem_file_path,
142            mem_prealloc,
143            libc::PROT_READ | libc::PROT_WRITE,
144            is_hotplug,
145        )
146    }
147
148    /// Create an address space region to map memory from memfd/hugetlbfs into the virtual machine.
149    ///
150    /// # Arguments
151    /// * `base` - Base address in VM to map content
152    /// * `size` - Length of content to map
153    /// * `numa_node_id` - Optional NUMA node id to allocate memory from
154    /// * `mem_type` - Memory mapping from, 'shmem' or 'hugetlbfs'
155    /// * `mem_file_path` - Memory file path
156    /// * `mem_prealloc` - Whether to enable pre-allocation of guest memory
157    /// * `is_hotplug` - Whether it's a region for hotplug.
158    /// * `prot_flags` - mmap protection flags
159    pub fn create_memory_region(
160        base: GuestAddress,
161        size: GuestUsize,
162        numa_node_id: Option<u32>,
163        mem_type: &str,
164        mem_file_path: &str,
165        mem_prealloc: bool,
166        prot_flags: i32,
167        is_hotplug: bool,
168    ) -> Result<AddressSpaceRegion, AddressSpaceError> {
169        let perm_flags = if mem_prealloc {
170            libc::MAP_SHARED | libc::MAP_POPULATE
171        } else {
172            libc::MAP_SHARED
173        };
174        let source_type = MemorySourceType::from_str(mem_type)
175            .map_err(|_e| AddressSpaceError::InvalidMemorySourceType(mem_type.to_string()))?;
176        let mut reg = match source_type {
177            MemorySourceType::MemFdShared | MemorySourceType::MemFdOnHugeTlbFs => {
178                let fn_str = if source_type == MemFdShared {
179                    CString::new("shmem").expect("CString::new('shmem') failed")
180                } else {
181                    CString::new("hugeshmem").expect("CString::new('hugeshmem') failed")
182                };
183                let filename = fn_str.as_c_str();
184                let fd = memfd::memfd_create(filename, memfd::MemFdCreateFlag::empty())
185                    .map_err(AddressSpaceError::CreateMemFd)?;
186                // Safe because we have just created the fd.
187                let file: File = unsafe { File::from_raw_fd(fd) };
188                file.set_len(size).map_err(AddressSpaceError::SetFileSize)?;
189                Self::build(
190                    AddressSpaceRegionType::DefaultMemory,
191                    base,
192                    size,
193                    numa_node_id,
194                    Some(FileOffset::new(file, 0)),
195                    perm_flags,
196                    prot_flags,
197                    is_hotplug,
198                )
199            }
200            MemorySourceType::MmapAnonymous | MemorySourceType::MmapAnonymousHugeTlbFs => {
201                let mut perm_flags = libc::MAP_PRIVATE | libc::MAP_ANONYMOUS;
202                if mem_prealloc {
203                    perm_flags |= libc::MAP_POPULATE
204                }
205                Self::build(
206                    AddressSpaceRegionType::DefaultMemory,
207                    base,
208                    size,
209                    numa_node_id,
210                    None,
211                    perm_flags,
212                    prot_flags,
213                    is_hotplug,
214                )
215            }
216            MemorySourceType::FileOnHugeTlbFs => {
217                let path = Path::new(mem_file_path);
218                if let Some(parent_dir) = path.parent() {
219                    // Ensure that the parent directory is existed for the mem file path.
220                    std::fs::create_dir_all(parent_dir).map_err(AddressSpaceError::CreateDir)?;
221                }
222                let file = OpenOptions::new()
223                    .read(true)
224                    .write(true)
225                    .create(true)
226                    .open(mem_file_path)
227                    .map_err(AddressSpaceError::OpenFile)?;
228                nix::unistd::unlink(mem_file_path).map_err(AddressSpaceError::UnlinkFile)?;
229                file.set_len(size).map_err(AddressSpaceError::SetFileSize)?;
230                let file_offset = FileOffset::new(file, 0);
231                Self::build(
232                    AddressSpaceRegionType::DefaultMemory,
233                    base,
234                    size,
235                    numa_node_id,
236                    Some(file_offset),
237                    perm_flags,
238                    prot_flags,
239                    is_hotplug,
240                )
241            }
242        };
243
244        if source_type.is_hugepage() {
245            reg.set_hugepage();
246        }
247        if source_type.is_mmap_anonymous() {
248            reg.set_anonpage();
249        }
250
251        Ok(reg)
252    }
253
254    /// Create an address region for device MMIO.
255    ///
256    /// # Arguments
257    /// * `base` - Base address in VM to map content
258    /// * `size` - Length of content to map
259    pub fn create_device_region(
260        base: GuestAddress,
261        size: GuestUsize,
262    ) -> Result<AddressSpaceRegion, AddressSpaceError> {
263        Ok(Self::build(
264            AddressSpaceRegionType::DeviceMemory,
265            base,
266            size,
267            None,
268            None,
269            0,
270            0,
271            false,
272        ))
273    }
274
275    /// Get type of the address space region.
276    pub fn region_type(&self) -> AddressSpaceRegionType {
277        self.ty
278    }
279
280    /// Get size of region.
281    pub fn len(&self) -> GuestUsize {
282        self.size
283    }
284
285    /// Get the inclusive start physical address of the region.
286    pub fn start_addr(&self) -> GuestAddress {
287        self.base
288    }
289
290    /// Get the inclusive end physical address of the region.
291    pub fn last_addr(&self) -> GuestAddress {
292        debug_assert!(self.size > 0 && self.base.checked_add(self.size).is_some());
293        GuestAddress(self.base.raw_value() + self.size - 1)
294    }
295
296    /// Get mmap permission flags of the address space region.
297    pub fn perm_flags(&self) -> i32 {
298        self.perm_flags
299    }
300
301    /// Set mmap permission flags for the address space region.
302    pub fn set_perm_flags(&mut self, perm_flags: i32) {
303        self.perm_flags = perm_flags;
304    }
305
306    /// Get mmap protection flags of the address space region.
307    pub fn prot_flags(&self) -> i32 {
308        self.prot_flags
309    }
310
311    /// Set mmap protection flags for the address space region.
312    pub fn set_prot_flags(&mut self, prot_flags: i32) {
313        self.prot_flags = prot_flags;
314    }
315
316    /// Get host_numa_node_id flags
317    pub fn host_numa_node_id(&self) -> Option<u32> {
318        self.host_numa_node_id
319    }
320
321    /// Set associated NUMA node ID to allocate memory from for this region.
322    pub fn set_host_numa_node_id(&mut self, host_numa_node_id: Option<u32>) {
323        self.host_numa_node_id = host_numa_node_id;
324    }
325
326    /// Check whether the address space region is backed by a memory file.
327    pub fn has_file(&self) -> bool {
328        self.file_offset.is_some()
329    }
330
331    /// Get optional file associated with the region.
332    pub fn file_offset(&self) -> Option<&FileOffset> {
333        self.file_offset.as_ref()
334    }
335
336    /// Set associated file/offset pair for the region.
337    pub fn set_file_offset(&mut self, file_offset: Option<FileOffset>) {
338        self.file_offset = file_offset;
339    }
340
341    /// Set the hotplug hint.
342    pub fn set_hotplug(&mut self) {
343        self.is_hotplug = true
344    }
345
346    /// Get the hotplug hint.
347    pub fn is_hotplug(&self) -> bool {
348        self.is_hotplug
349    }
350
351    /// Set hugepage hint for `madvise()`, only takes effect when the memory type is `shmem`.
352    pub fn set_hugepage(&mut self) {
353        self.is_hugepage = true
354    }
355
356    /// Get the hugepage hint.
357    pub fn is_hugepage(&self) -> bool {
358        self.is_hugepage
359    }
360
361    /// Set the anonymous memory hint.
362    pub fn set_anonpage(&mut self) {
363        self.is_anon = true
364    }
365
366    /// Get the anonymous memory hint.
367    pub fn is_anonpage(&self) -> bool {
368        self.is_anon
369    }
370
371    /// Check whether the address space region is valid.
372    pub fn is_valid(&self) -> bool {
373        self.size > 0 && self.base.checked_add(self.size).is_some()
374    }
375
376    /// Check whether the address space region intersects with another one.
377    pub fn intersect_with(&self, other: &AddressSpaceRegion) -> bool {
378        // Treat invalid address region as intersecting always
379        let end1 = match self.base.checked_add(self.size) {
380            Some(addr) => addr,
381            None => return true,
382        };
383        let end2 = match other.base.checked_add(other.size) {
384            Some(addr) => addr,
385            None => return true,
386        };
387
388        !(end1 <= other.base || self.base >= end2)
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    use super::*;
395    use std::io::Write;
396    use vmm_sys_util::tempfile::TempFile;
397
398    #[test]
399    fn test_address_space_region_valid() {
400        let reg1 = AddressSpaceRegion::new(
401            AddressSpaceRegionType::DefaultMemory,
402            GuestAddress(0xFFFFFFFFFFFFF000),
403            0x2000,
404        );
405        assert!(!reg1.is_valid());
406        let reg1 = AddressSpaceRegion::new(
407            AddressSpaceRegionType::DefaultMemory,
408            GuestAddress(0xFFFFFFFFFFFFF000),
409            0x1000,
410        );
411        assert!(!reg1.is_valid());
412        let reg1 = AddressSpaceRegion::new(
413            AddressSpaceRegionType::DeviceMemory,
414            GuestAddress(0xFFFFFFFFFFFFE000),
415            0x1000,
416        );
417        assert!(reg1.is_valid());
418        assert_eq!(reg1.start_addr(), GuestAddress(0xFFFFFFFFFFFFE000));
419        assert_eq!(reg1.len(), 0x1000);
420        assert!(!reg1.has_file());
421        assert!(reg1.file_offset().is_none());
422        assert_eq!(reg1.perm_flags(), libc::MAP_SHARED);
423        assert_eq!(reg1.prot_flags(), libc::PROT_READ | libc::PROT_WRITE);
424        assert_eq!(reg1.region_type(), AddressSpaceRegionType::DeviceMemory);
425
426        let tmp_file = TempFile::new().unwrap();
427        let mut f = tmp_file.into_file();
428        let sample_buf = &[1, 2, 3, 4, 5];
429        assert!(f.write_all(sample_buf).is_ok());
430        let reg2 = AddressSpaceRegion::build(
431            AddressSpaceRegionType::DefaultMemory,
432            GuestAddress(0x1000),
433            0x1000,
434            None,
435            Some(FileOffset::new(f, 0x0)),
436            0x5a,
437            0x5a,
438            false,
439        );
440        assert_eq!(reg2.region_type(), AddressSpaceRegionType::DefaultMemory);
441        assert!(reg2.is_valid());
442        assert_eq!(reg2.start_addr(), GuestAddress(0x1000));
443        assert_eq!(reg2.len(), 0x1000);
444        assert!(reg2.has_file());
445        assert!(reg2.file_offset().is_some());
446        assert_eq!(reg2.perm_flags(), 0x5a);
447        assert_eq!(reg2.prot_flags(), 0x5a);
448    }
449
450    #[test]
451    fn test_address_space_region_intersect() {
452        let reg1 = AddressSpaceRegion::new(
453            AddressSpaceRegionType::DefaultMemory,
454            GuestAddress(0x1000),
455            0x1000,
456        );
457        let reg2 = AddressSpaceRegion::new(
458            AddressSpaceRegionType::DefaultMemory,
459            GuestAddress(0x2000),
460            0x1000,
461        );
462        let reg3 = AddressSpaceRegion::new(
463            AddressSpaceRegionType::DefaultMemory,
464            GuestAddress(0x1000),
465            0x1001,
466        );
467        let reg4 = AddressSpaceRegion::new(
468            AddressSpaceRegionType::DefaultMemory,
469            GuestAddress(0x1100),
470            0x100,
471        );
472        let reg5 = AddressSpaceRegion::new(
473            AddressSpaceRegionType::DefaultMemory,
474            GuestAddress(0xFFFFFFFFFFFFF000),
475            0x2000,
476        );
477
478        assert!(!reg1.intersect_with(&reg2));
479        assert!(!reg2.intersect_with(&reg1));
480
481        // intersect with self
482        assert!(reg1.intersect_with(&reg1));
483
484        // intersect with others
485        assert!(reg3.intersect_with(&reg2));
486        assert!(reg2.intersect_with(&reg3));
487        assert!(reg1.intersect_with(&reg4));
488        assert!(reg4.intersect_with(&reg1));
489        assert!(reg1.intersect_with(&reg5));
490        assert!(reg5.intersect_with(&reg1));
491    }
492
493    #[test]
494    fn test_create_device_region() {
495        let reg = AddressSpaceRegion::create_device_region(GuestAddress(0x10000), 0x1000).unwrap();
496        assert_eq!(reg.region_type(), AddressSpaceRegionType::DeviceMemory);
497        assert_eq!(reg.start_addr(), GuestAddress(0x10000));
498        assert_eq!(reg.len(), 0x1000);
499    }
500
501    #[test]
502    fn test_create_default_memory_region() {
503        AddressSpaceRegion::create_default_memory_region(
504            GuestAddress(0x100000),
505            0x100000,
506            None,
507            "invalid",
508            "invalid",
509            false,
510            false,
511        )
512        .unwrap_err();
513
514        let reg = AddressSpaceRegion::create_default_memory_region(
515            GuestAddress(0x100000),
516            0x100000,
517            None,
518            "shmem",
519            "",
520            false,
521            false,
522        )
523        .unwrap();
524        assert_eq!(reg.region_type(), AddressSpaceRegionType::DefaultMemory);
525        assert_eq!(reg.start_addr(), GuestAddress(0x100000));
526        assert_eq!(reg.last_addr(), GuestAddress(0x1fffff));
527        assert_eq!(reg.len(), 0x100000);
528        assert!(reg.file_offset().is_some());
529
530        let reg = AddressSpaceRegion::create_default_memory_region(
531            GuestAddress(0x100000),
532            0x100000,
533            None,
534            "hugeshmem",
535            "",
536            true,
537            false,
538        )
539        .unwrap();
540        assert_eq!(reg.region_type(), AddressSpaceRegionType::DefaultMemory);
541        assert_eq!(reg.start_addr(), GuestAddress(0x100000));
542        assert_eq!(reg.last_addr(), GuestAddress(0x1fffff));
543        assert_eq!(reg.len(), 0x100000);
544        assert!(reg.file_offset().is_some());
545
546        let reg = AddressSpaceRegion::create_default_memory_region(
547            GuestAddress(0x100000),
548            0x100000,
549            None,
550            "mmap",
551            "",
552            true,
553            false,
554        )
555        .unwrap();
556        assert_eq!(reg.region_type(), AddressSpaceRegionType::DefaultMemory);
557        assert_eq!(reg.start_addr(), GuestAddress(0x100000));
558        assert_eq!(reg.last_addr(), GuestAddress(0x1fffff));
559        assert_eq!(reg.len(), 0x100000);
560        assert!(reg.file_offset().is_none());
561
562        // TODO: test hugetlbfs
563    }
564}