diff --git a/kernel/comps/block/src/bio.rs b/kernel/comps/block/src/bio.rs index 57e6e5b3b..996c6a5fa 100644 --- a/kernel/comps/block/src/bio.rs +++ b/kernel/comps/block/src/bio.rs @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MPL-2.0 use align_ext::AlignExt; +use aster_util::segment_slice::SegmentSlice; use int_to_c_enum::TryFromInt; use ostd::{ - mm::{Frame, Infallible, Segment, VmReader, VmWriter}, + mm::{Frame, Infallible, VmReader, VmWriter}, sync::WaitQueue, }; @@ -366,7 +367,7 @@ pub enum BioStatus { #[derive(Debug, Clone)] pub struct BioSegment { /// The contiguous pages on which this segment resides. - pages: Segment, + pages: SegmentSlice, /// The starting offset (in bytes) within the first page. /// The offset should always be aligned to the sector size and /// must not exceed the size of a single page. @@ -381,9 +382,8 @@ const SECTOR_SIZE: u16 = super::SECTOR_SIZE as u16; impl<'a> BioSegment { /// Constructs a new `BioSegment` from `Segment`. - pub fn from_segment(segment: Segment, offset: usize, len: usize) -> Self { + pub fn from_segment(segment: SegmentSlice, offset: usize, len: usize) -> Self { assert!(offset + len <= segment.nbytes()); - Self { pages: segment.range(frame_range(&(offset..offset + len))), offset: AlignedUsize::::new(offset % super::BLOCK_SIZE).unwrap(), @@ -396,7 +396,7 @@ impl<'a> BioSegment { assert!(offset + len <= super::BLOCK_SIZE); Self { - pages: Segment::from(frame), + pages: SegmentSlice::from(frame), offset: AlignedUsize::::new(offset).unwrap(), len: AlignedUsize::::new(len).unwrap(), } @@ -418,7 +418,7 @@ impl<'a> BioSegment { } /// Returns the contiguous pages on which this segment resides. - pub fn pages(&self) -> &Segment { + pub fn pages(&self) -> &SegmentSlice { &self.pages } diff --git a/kernel/comps/block/src/impl_block_device.rs b/kernel/comps/block/src/impl_block_device.rs index c145086ff..64d6bd299 100644 --- a/kernel/comps/block/src/impl_block_device.rs +++ b/kernel/comps/block/src/impl_block_device.rs @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MPL-2.0 +use aster_util::segment_slice::SegmentSlice; use ostd::mm::{ - FallibleVmRead, FallibleVmWrite, Frame, FrameAllocOptions, Segment, VmIo, VmReader, VmWriter, + FallibleVmRead, FallibleVmWrite, Frame, FrameAllocOptions, VmIo, VmReader, VmWriter, }; use super::{ @@ -16,7 +17,11 @@ use crate::prelude::*; // TODO: Add API to submit bio with multiple segments in scatter/gather manner. impl dyn BlockDevice { /// Synchronously reads contiguous blocks starting from the `bid`. - pub fn read_blocks(&self, bid: Bid, segment: &Segment) -> Result { + pub fn read_blocks( + &self, + bid: Bid, + segment: &SegmentSlice, + ) -> Result { let bio = create_bio_from_segment(BioType::Read, bid, segment); let status = bio.submit_and_wait(self)?; Ok(status) @@ -26,7 +31,7 @@ impl dyn BlockDevice { pub fn read_blocks_async( &self, bid: Bid, - segment: &Segment, + segment: &SegmentSlice, ) -> Result { let bio = create_bio_from_segment(BioType::Read, bid, segment); bio.submit(self) @@ -46,7 +51,11 @@ impl dyn BlockDevice { } /// Synchronously writes contiguous blocks starting from the `bid`. - pub fn write_blocks(&self, bid: Bid, segment: &Segment) -> Result { + pub fn write_blocks( + &self, + bid: Bid, + segment: &SegmentSlice, + ) -> Result { let bio = create_bio_from_segment(BioType::Write, bid, segment); let status = bio.submit_and_wait(self)?; Ok(status) @@ -56,7 +65,7 @@ impl dyn BlockDevice { pub fn write_blocks_async( &self, bid: Bid, - segment: &Segment, + segment: &SegmentSlice, ) -> Result { let bio = create_bio_from_segment(BioType::Write, bid, segment); bio.submit(self) @@ -96,7 +105,8 @@ impl VmIo for dyn BlockDevice { let segment = FrameAllocOptions::new(num_blocks as usize) .uninit(true) .alloc_contiguous()?; - let bio_segment = BioSegment::from_segment(segment, offset % BLOCK_SIZE, read_len); + let bio_segment = + BioSegment::from_segment(segment.into(), offset % BLOCK_SIZE, read_len); ( Bio::new( @@ -147,7 +157,7 @@ impl VmIo for dyn BlockDevice { .skip(offset % BLOCK_SIZE) .write_fallible(reader) .map_err(|(e, _)| e)?; - let bio_segment = BioSegment::from_segment(segment, offset % BLOCK_SIZE, len); + let bio_segment = BioSegment::from_segment(segment.into(), offset % BLOCK_SIZE, len); Bio::new( BioType::Write, Sid::from_offset(offset), @@ -188,7 +198,7 @@ impl dyn BlockDevice { .writer() .skip(offset % BLOCK_SIZE) .write(&mut buf.into()); - let bio_segment = BioSegment::from_segment(segment, offset % BLOCK_SIZE, len); + let bio_segment = BioSegment::from_segment(segment.into(), offset % BLOCK_SIZE, len); Bio::new( BioType::Write, Sid::from_offset(offset), @@ -203,7 +213,7 @@ impl dyn BlockDevice { } // TODO: Maybe we should have a builder for `Bio`. -fn create_bio_from_segment(type_: BioType, bid: Bid, segment: &Segment) -> Bio { +fn create_bio_from_segment(type_: BioType, bid: Bid, segment: &SegmentSlice) -> Bio { let bio_segment = BioSegment::from_segment(segment.clone(), 0, segment.nbytes()); Bio::new( type_, diff --git a/kernel/comps/network/src/dma_pool.rs b/kernel/comps/network/src/dma_pool.rs index 072abd4bf..fe74b13e9 100644 --- a/kernel/comps/network/src/dma_pool.rs +++ b/kernel/comps/network/src/dma_pool.rs @@ -152,9 +152,9 @@ impl DmaPage { pool: Weak, ) -> Result { let dma_stream = { - let vm_segment = FrameAllocOptions::new(1).alloc_contiguous()?; + let segment = FrameAllocOptions::new(1).alloc_contiguous()?; - DmaStream::map(vm_segment, direction, is_cache_coherent) + DmaStream::map(segment, direction, is_cache_coherent) .map_err(|_| ostd::Error::AccessDenied)? }; diff --git a/kernel/comps/virtio/src/device/block/device.rs b/kernel/comps/virtio/src/device/block/device.rs index 4a7ceecb1..fefd2d4a0 100644 --- a/kernel/comps/virtio/src/device/block/device.rs +++ b/kernel/comps/virtio/src/device/block/device.rs @@ -419,7 +419,8 @@ impl DeviceInner { .flat_map(|bio| { bio.segments().iter().map(|segment| { let dma_stream = - DmaStream::map(segment.pages().clone(), dma_direction, false).unwrap(); + DmaStream::map(segment.pages().clone().into(), dma_direction, false) + .unwrap(); (dma_stream, segment.offset(), segment.nbytes()) }) }) diff --git a/kernel/comps/virtio/src/queue.rs b/kernel/comps/virtio/src/queue.rs index 38e5e0652..905da0986 100644 --- a/kernel/comps/virtio/src/queue.rs +++ b/kernel/comps/virtio/src/queue.rs @@ -81,10 +81,8 @@ impl VirtQueue { let desc_size = size_of::() * size as usize; let (seg1, seg2) = { - let continue_segment = FrameAllocOptions::new(2).alloc_contiguous().unwrap(); - let seg1 = continue_segment.range(0..1); - let seg2 = continue_segment.range(1..2); - (seg1, seg2) + let segment = FrameAllocOptions::new(2).alloc_contiguous().unwrap(); + segment.split(ostd::mm::PAGE_SIZE) }; let desc_frame_ptr: SafePtr = SafePtr::new(DmaCoherent::map(seg1, true).unwrap(), 0); diff --git a/kernel/libs/aster-util/src/lib.rs b/kernel/libs/aster-util/src/lib.rs index fecdf1360..e2b0129d7 100644 --- a/kernel/libs/aster-util/src/lib.rs +++ b/kernel/libs/aster-util/src/lib.rs @@ -10,5 +10,6 @@ extern crate alloc; pub mod coeff; pub mod dup; pub mod safe_ptr; +pub mod segment_slice; pub mod slot_vec; pub mod union_read_ptr; diff --git a/kernel/libs/aster-util/src/segment_slice.rs b/kernel/libs/aster-util/src/segment_slice.rs new file mode 100644 index 000000000..c7e73f8ac --- /dev/null +++ b/kernel/libs/aster-util/src/segment_slice.rs @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: MPL-2.0 + +// SPDX-License-Identifier: MPL-2.0 + +//! Provides [`SegmentSlice`] for quick duplication and slicing over [`Segment`]. + +use alloc::sync::Arc; +use core::ops::Range; + +use ostd::{ + mm::{ + FallibleVmRead, FallibleVmWrite, Frame, Infallible, Paddr, Segment, VmIo, VmReader, + VmWriter, PAGE_SIZE, + }, + Error, Result, +}; + +/// A reference to a slice of a [`Segment`]. +/// +/// Cloning a [`SegmentSlice`] is cheap, as it only increments one reference +/// count. While cloning a [`Segment`] will increment the reference count of +/// many underlying pages. +/// +/// The downside is that the [`SegmentSlice`] requires heap allocation. Also, +/// if any [`SegmentSlice`] of the original [`Segment`] is alive, all pages in +/// the original [`Segment`], including the pages that are not referenced, will +/// not be freed. +#[derive(Debug, Clone)] +pub struct SegmentSlice { + inner: Arc, + range: Range, +} + +impl SegmentSlice { + /// Returns a part of the `Segment`. + /// + /// # Panics + /// + /// If `range` is not within the range of this `Segment`, + /// then the method panics. + pub fn range(&self, range: Range) -> Self { + let orig_range = &self.range; + let adj_range = (range.start + orig_range.start)..(range.end + orig_range.start); + assert!(!adj_range.is_empty() && adj_range.end <= orig_range.end); + + Self { + inner: self.inner.clone(), + range: adj_range, + } + } + + /// Returns the start physical address. + pub fn start_paddr(&self) -> Paddr { + self.start_frame_index() * PAGE_SIZE + } + + /// Returns the end physical address. + pub fn end_paddr(&self) -> Paddr { + (self.start_frame_index() + self.nframes()) * PAGE_SIZE + } + + /// Returns the number of page frames. + pub fn nframes(&self) -> usize { + self.range.len() + } + + /// Returns the number of bytes. + pub fn nbytes(&self) -> usize { + self.nframes() * PAGE_SIZE + } + + /// Gets a reader for the slice. + pub fn reader(&self) -> VmReader<'_, Infallible> { + self.inner + .reader() + .skip(self.start_paddr() - self.inner.start_paddr()) + .limit(self.nbytes()) + } + + /// Gets a writer for the slice. + pub fn writer(&self) -> VmWriter<'_, Infallible> { + self.inner + .writer() + .skip(self.start_paddr() - self.inner.start_paddr()) + .limit(self.nbytes()) + } + + fn start_frame_index(&self) -> usize { + self.inner.start_paddr() / PAGE_SIZE + self.range.start + } +} + +impl VmIo for SegmentSlice { + fn read(&self, offset: usize, writer: &mut VmWriter) -> Result<()> { + let read_len = writer.avail(); + // Do bound check with potential integer overflow in mind + let max_offset = offset.checked_add(read_len).ok_or(Error::Overflow)?; + if max_offset > self.nbytes() { + return Err(Error::InvalidArgs); + } + let len = self + .reader() + .skip(offset) + .read_fallible(writer) + .map_err(|(e, _)| e)?; + debug_assert!(len == read_len); + Ok(()) + } + + fn write(&self, offset: usize, reader: &mut VmReader) -> Result<()> { + let write_len = reader.remain(); + // Do bound check with potential integer overflow in mind + let max_offset = offset.checked_add(reader.remain()).ok_or(Error::Overflow)?; + if max_offset > self.nbytes() { + return Err(Error::InvalidArgs); + } + let len = self + .writer() + .skip(offset) + .write_fallible(reader) + .map_err(|(e, _)| e)?; + debug_assert!(len == write_len); + Ok(()) + } +} + +impl From for SegmentSlice { + fn from(segment: Segment) -> Self { + Self { + inner: Arc::new(segment), + range: 0..1, + } + } +} + +impl From for Segment { + fn from(slice: SegmentSlice) -> Self { + let start = slice.range.start * PAGE_SIZE; + let end = slice.range.end * PAGE_SIZE; + slice.inner.slice(&(start..end)) + } +} + +impl From for SegmentSlice { + fn from(frame: Frame) -> Self { + SegmentSlice::from(Segment::from(frame)) + } +} diff --git a/kernel/src/fs/exfat/mod.rs b/kernel/src/fs/exfat/mod.rs index c804daa71..ad60f77aa 100644 --- a/kernel/src/fs/exfat/mod.rs +++ b/kernel/src/fs/exfat/mod.rs @@ -22,7 +22,7 @@ mod test { BlockDevice, BlockDeviceMeta, }; use ostd::{ - mm::{FrameAllocOptions, Segment, VmIo}, + mm::{FrameAllocOptions, Segment, VmIo, PAGE_SIZE}, prelude::*, }; use rand::{rngs::SmallRng, RngCore, SeedableRng}; @@ -48,7 +48,7 @@ mod test { } pub fn sectors_count(&self) -> usize { - self.0.nframes() * (PAGE_SIZE / SECTOR_SIZE) + self.0.nbytes() / SECTOR_SIZE } } @@ -111,13 +111,10 @@ mod test { /// Read exfat disk image fn new_vm_segment_from_image() -> Segment { - let vm_segment = { - FrameAllocOptions::new(EXFAT_IMAGE.len() / PAGE_SIZE) - .is_contiguous(true) - .uninit(true) - .alloc_contiguous() - .unwrap() - }; + let vm_segment = FrameAllocOptions::new(EXFAT_IMAGE.len().div_ceil(PAGE_SIZE)) + .uninit(true) + .alloc_contiguous() + .unwrap(); vm_segment.write_bytes(0, EXFAT_IMAGE).unwrap(); vm_segment diff --git a/kernel/src/fs/ext2/block_group.rs b/kernel/src/fs/ext2/block_group.rs index ccc2368e2..c076060ea 100644 --- a/kernel/src/fs/ext2/block_group.rs +++ b/kernel/src/fs/ext2/block_group.rs @@ -28,7 +28,7 @@ struct BlockGroupImpl { impl BlockGroup { /// Loads and constructs a block group. pub fn load( - group_descriptors_segment: &Segment, + group_descriptors_segment: &SegmentSlice, idx: usize, block_device: &dyn BlockDevice, super_block: &SuperBlock, diff --git a/kernel/src/fs/ext2/fs.rs b/kernel/src/fs/ext2/fs.rs index b5d6392db..1d0f573c9 100644 --- a/kernel/src/fs/ext2/fs.rs +++ b/kernel/src/fs/ext2/fs.rs @@ -23,7 +23,7 @@ pub struct Ext2 { blocks_per_group: Ext2Bid, inode_size: usize, block_size: usize, - group_descriptors_segment: Segment, + group_descriptors_segment: SegmentSlice, self_ref: Weak, } @@ -48,7 +48,8 @@ impl Ext2 { .div_ceil(BLOCK_SIZE); let segment = FrameAllocOptions::new(npages) .uninit(true) - .alloc_contiguous()?; + .alloc_contiguous()? + .into(); match block_device.read_blocks(super_block.group_descriptors_bid(0), &segment)? { BioStatus::Complete => (), err_status => { @@ -61,7 +62,7 @@ impl Ext2 { // Load the block groups information let load_block_groups = |fs: Weak, block_device: &dyn BlockDevice, - group_descriptors_segment: &Segment| + group_descriptors_segment: &SegmentSlice| -> Result> { let block_groups_count = super_block.block_groups_count() as usize; let mut block_groups = Vec::with_capacity(block_groups_count); @@ -297,7 +298,7 @@ impl Ext2 { } /// Reads contiguous blocks starting from the `bid` synchronously. - pub(super) fn read_blocks(&self, bid: Ext2Bid, segment: &Segment) -> Result<()> { + pub(super) fn read_blocks(&self, bid: Ext2Bid, segment: &SegmentSlice) -> Result<()> { let status = self .block_device .read_blocks(Bid::new(bid as u64), segment)?; @@ -308,7 +309,11 @@ impl Ext2 { } /// Reads contiguous blocks starting from the `bid` asynchronously. - pub(super) fn read_blocks_async(&self, bid: Ext2Bid, segment: &Segment) -> Result { + pub(super) fn read_blocks_async( + &self, + bid: Ext2Bid, + segment: &SegmentSlice, + ) -> Result { let waiter = self .block_device .read_blocks_async(Bid::new(bid as u64), segment)?; @@ -333,7 +338,7 @@ impl Ext2 { } /// Writes contiguous blocks starting from the `bid` synchronously. - pub(super) fn write_blocks(&self, bid: Ext2Bid, segment: &Segment) -> Result<()> { + pub(super) fn write_blocks(&self, bid: Ext2Bid, segment: &SegmentSlice) -> Result<()> { let status = self .block_device .write_blocks(Bid::new(bid as u64), segment)?; @@ -344,7 +349,11 @@ impl Ext2 { } /// Writes contiguous blocks starting from the `bid` asynchronously. - pub(super) fn write_blocks_async(&self, bid: Ext2Bid, segment: &Segment) -> Result { + pub(super) fn write_blocks_async( + &self, + bid: Ext2Bid, + segment: &SegmentSlice, + ) -> Result { let waiter = self .block_device .write_blocks_async(Bid::new(bid as u64), segment)?; diff --git a/kernel/src/fs/ext2/inode.rs b/kernel/src/fs/ext2/inode.rs index 9f44fb059..0a168325b 100644 --- a/kernel/src/fs/ext2/inode.rs +++ b/kernel/src/fs/ext2/inode.rs @@ -944,7 +944,8 @@ impl Inner { let buf_nblocks = read_len / BLOCK_SIZE; let segment = FrameAllocOptions::new(buf_nblocks) .uninit(true) - .alloc_contiguous()?; + .alloc_contiguous()? + .into(); self.inode_impl.read_blocks(start_bid, &segment)?; segment.read(0, writer)?; @@ -986,7 +987,7 @@ impl Inner { .uninit(true) .alloc_contiguous()?; segment.write(0, reader)?; - segment + segment.into() }; self.inode_impl.write_blocks(start_bid, &segment)?; @@ -1128,7 +1129,7 @@ impl InodeImpl_ { self.inode().fs() } - pub fn read_blocks_async(&self, bid: Ext2Bid, blocks: &Segment) -> Result { + pub fn read_blocks_async(&self, bid: Ext2Bid, blocks: &SegmentSlice) -> Result { let nblocks = blocks.nframes(); let mut segments = Vec::new(); @@ -1183,14 +1184,14 @@ impl InodeImpl_ { Ok(bio_waiter) } - pub fn read_blocks(&self, bid: Ext2Bid, blocks: &Segment) -> Result<()> { + pub fn read_blocks(&self, bid: Ext2Bid, blocks: &SegmentSlice) -> Result<()> { match self.read_blocks_async(bid, blocks)?.wait() { Some(BioStatus::Complete) => Ok(()), _ => return_errno!(Errno::EIO), } } - pub fn write_blocks_async(&self, bid: Ext2Bid, blocks: &Segment) -> Result { + pub fn write_blocks_async(&self, bid: Ext2Bid, blocks: &SegmentSlice) -> Result { let nblocks = blocks.nframes(); let mut bio_waiter = BioWaiter::new(); @@ -1214,7 +1215,7 @@ impl InodeImpl_ { Ok(bio_waiter) } - pub fn write_blocks(&self, bid: Ext2Bid, blocks: &Segment) -> Result<()> { + pub fn write_blocks(&self, bid: Ext2Bid, blocks: &SegmentSlice) -> Result<()> { match self.write_blocks_async(bid, blocks)?.wait() { Some(BioStatus::Complete) => Ok(()), _ => return_errno!(Errno::EIO), @@ -1873,20 +1874,20 @@ impl InodeImpl { } /// Reads one or multiple blocks to the segment start from `bid` asynchronously. - pub fn read_blocks_async(&self, bid: Ext2Bid, blocks: &Segment) -> Result { + pub fn read_blocks_async(&self, bid: Ext2Bid, blocks: &SegmentSlice) -> Result { self.0.read().read_blocks_async(bid, blocks) } - pub fn read_blocks(&self, bid: Ext2Bid, blocks: &Segment) -> Result<()> { + pub fn read_blocks(&self, bid: Ext2Bid, blocks: &SegmentSlice) -> Result<()> { self.0.read().read_blocks(bid, blocks) } /// Writes one or multiple blocks from the segment start from `bid` asynchronously. - pub fn write_blocks_async(&self, bid: Ext2Bid, blocks: &Segment) -> Result { + pub fn write_blocks_async(&self, bid: Ext2Bid, blocks: &SegmentSlice) -> Result { self.0.read().write_blocks_async(bid, blocks) } - pub fn write_blocks(&self, bid: Ext2Bid, blocks: &Segment) -> Result<()> { + pub fn write_blocks(&self, bid: Ext2Bid, blocks: &SegmentSlice) -> Result<()> { self.0.read().write_blocks(bid, blocks) } @@ -1962,7 +1963,9 @@ impl InodeImpl { // TODO: If we can persist the `blocks_hole_desc`, Can we avoid zeroing all the holes on the device? debug_assert!(max_batch_len > 0); - let zeroed_segment = FrameAllocOptions::new(max_batch_len).alloc_contiguous()?; + let zeroed_segment: SegmentSlice = FrameAllocOptions::new(max_batch_len) + .alloc_contiguous()? + .into(); for (start_bid, batch_len) in data_hole_batches { inner.write_blocks(start_bid, &zeroed_segment.range(0..batch_len))?; } @@ -2001,12 +2004,12 @@ impl InodeImpl { impl PageCacheBackend for InodeImpl { fn read_page_async(&self, idx: usize, frame: &Frame) -> Result { let bid = idx as Ext2Bid; - self.read_blocks_async(bid, &Segment::from(frame.clone())) + self.read_blocks_async(bid, &SegmentSlice::from(frame.clone())) } fn write_page_async(&self, idx: usize, frame: &Frame) -> Result { let bid = idx as Ext2Bid; - self.write_blocks_async(bid, &Segment::from(frame.clone())) + self.write_blocks_async(bid, &SegmentSlice::from(frame.clone())) } fn npages(&self) -> usize { diff --git a/kernel/src/fs/ext2/prelude.rs b/kernel/src/fs/ext2/prelude.rs index bd6847134..eaf376002 100644 --- a/kernel/src/fs/ext2/prelude.rs +++ b/kernel/src/fs/ext2/prelude.rs @@ -12,8 +12,9 @@ pub(super) use aster_block::{ BlockDevice, BLOCK_SIZE, }; pub(super) use aster_rights::Full; +pub(super) use aster_util::segment_slice::SegmentSlice; pub(super) use ostd::{ - mm::{Frame, FrameAllocOptions, Segment, VmIo}, + mm::{Frame, FrameAllocOptions, VmIo}, sync::{RwMutex, RwMutexReadGuard, RwMutexWriteGuard}, }; pub(super) use static_assertions::const_assert; diff --git a/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs b/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs index 7c7c9f434..06e0d7280 100644 --- a/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs +++ b/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs @@ -35,13 +35,15 @@ pub fn main() { } fn create_user_space(program: &[u8]) -> UserSpace { - let nframes = program.len().align_up(PAGE_SIZE) / PAGE_SIZE; + let nbytes = program.len().align_up(PAGE_SIZE); let user_pages = { - let vm_frames = FrameAllocOptions::new(nframes).alloc().unwrap(); + let segment = FrameAllocOptions::new(nbytes / PAGE_SIZE) + .alloc_contiguous() + .unwrap(); // Physical memory pages can be only accessed - // via the Frame abstraction. - vm_frames.write_bytes(0, program).unwrap(); - vm_frames + // via the `Frame` or `Segment` abstraction. + segment.write_bytes(0, program).unwrap(); + segment }; let user_address_space = { const MAP_ADDR: Vaddr = 0x0040_0000; // The map addr for statically-linked executable @@ -50,9 +52,7 @@ fn create_user_space(program: &[u8]) -> UserSpace { // created and manipulated safely through // the `VmSpace` abstraction. let vm_space = VmSpace::new(); - let mut cursor = vm_space - .cursor_mut(&(MAP_ADDR..MAP_ADDR + nframes * PAGE_SIZE)) - .unwrap(); + let mut cursor = vm_space.cursor_mut(&(MAP_ADDR..MAP_ADDR + nbytes)).unwrap(); let map_prop = PageProperty::new(PageFlags::RWX, CachePolicy::Writeback); for frame in user_pages { cursor.map(frame, map_prop); diff --git a/ostd/src/mm/dma/dma_coherent.rs b/ostd/src/mm/dma/dma_coherent.rs index 1a7cbb526..0c2dd4542 100644 --- a/ostd/src/mm/dma/dma_coherent.rs +++ b/ostd/src/mm/dma/dma_coherent.rs @@ -57,7 +57,7 @@ impl DmaCoherent { vm_segment: Segment, is_cache_coherent: bool, ) -> core::result::Result { - let frame_count = vm_segment.nframes(); + let frame_count = vm_segment.nbytes() / PAGE_SIZE; let start_paddr = vm_segment.start_paddr(); if !check_and_insert_dma_mapping(start_paddr, frame_count) { return Err(DmaError::AlreadyMapped); @@ -109,6 +109,11 @@ impl DmaCoherent { }), }) } + + /// Returns the number of bytes in the DMA mapping. + pub fn nbytes(&self) -> usize { + self.inner.vm_segment.nbytes() + } } impl HasDaddr for DmaCoherent { @@ -126,7 +131,7 @@ impl Deref for DmaCoherent { impl Drop for DmaCoherentInner { fn drop(&mut self) { - let frame_count = self.vm_segment.nframes(); + let frame_count = self.vm_segment.nbytes() / PAGE_SIZE; let start_paddr = self.vm_segment.start_paddr(); // Ensure that the addresses used later will not overflow start_paddr.checked_add(frame_count * PAGE_SIZE).unwrap(); @@ -244,7 +249,7 @@ mod test { .is_contiguous(true) .alloc_contiguous() .unwrap(); - let vm_segment_child = vm_segment_parent.range(0..1); + let vm_segment_child = vm_segment_parent.slice(&(0..PAGE_SIZE)); let _dma_coherent_parent = DmaCoherent::map(vm_segment_parent, false); let dma_coherent_child = DmaCoherent::map(vm_segment_child, false); assert!(dma_coherent_child.is_err()); diff --git a/ostd/src/mm/dma/dma_stream.rs b/ostd/src/mm/dma/dma_stream.rs index 323d3825b..3f8cb6635 100644 --- a/ostd/src/mm/dma/dma_stream.rs +++ b/ostd/src/mm/dma/dma_stream.rs @@ -63,7 +63,7 @@ impl DmaStream { direction: DmaDirection, is_cache_coherent: bool, ) -> Result { - let frame_count = vm_segment.nframes(); + let frame_count = vm_segment.nbytes() / PAGE_SIZE; let start_paddr = vm_segment.start_paddr(); if !check_and_insert_dma_mapping(start_paddr, frame_count) { return Err(DmaError::AlreadyMapped); @@ -119,7 +119,7 @@ impl DmaStream { /// Returns the number of frames pub fn nframes(&self) -> usize { - self.inner.vm_segment.nframes() + self.inner.vm_segment.nbytes() / PAGE_SIZE } /// Returns the number of bytes @@ -171,7 +171,7 @@ impl HasDaddr for DmaStream { impl Drop for DmaStreamInner { fn drop(&mut self) { - let frame_count = self.vm_segment.nframes(); + let frame_count = self.vm_segment.nbytes() / PAGE_SIZE; let start_paddr = self.vm_segment.start_paddr(); // Ensure that the addresses used later will not overflow start_paddr.checked_add(frame_count * PAGE_SIZE).unwrap(); @@ -333,7 +333,7 @@ mod test { .is_contiguous(true) .alloc_contiguous() .unwrap(); - let vm_segment_child = vm_segment_parent.range(0..1); + let vm_segment_child = vm_segment_parent.slice(&(0..PAGE_SIZE)); let dma_stream_parent = DmaStream::map(vm_segment_parent, DmaDirection::Bidirectional, false); let dma_stream_child = DmaStream::map(vm_segment_child, DmaDirection::Bidirectional, false); diff --git a/ostd/src/mm/frame/mod.rs b/ostd/src/mm/frame/mod.rs index dc0123686..914e93231 100644 --- a/ostd/src/mm/frame/mod.rs +++ b/ostd/src/mm/frame/mod.rs @@ -9,23 +9,20 @@ //! read and written by the kernel or the user. pub mod options; -pub mod segment; +mod segment; use core::mem::ManuallyDrop; pub use segment::Segment; -use super::{ - page::{ - meta::{FrameMeta, MetaSlot, PageMeta, PageUsage}, - DynPage, Page, - }, - Infallible, +use super::page::{ + meta::{FrameMeta, MetaSlot, PageMeta, PageUsage}, + DynPage, Page, }; use crate::{ mm::{ io::{FallibleVmRead, FallibleVmWrite, VmIo, VmReader, VmWriter}, - paddr_to_vaddr, HasPaddr, Paddr, PAGE_SIZE, + paddr_to_vaddr, HasPaddr, Infallible, Paddr, PAGE_SIZE, }, Error, Result, }; @@ -181,54 +178,6 @@ impl VmIo for Frame { } } -impl VmIo for alloc::vec::Vec { - fn read(&self, offset: usize, writer: &mut VmWriter) -> Result<()> { - // Do bound check with potential integer overflow in mind - let max_offset = offset.checked_add(writer.avail()).ok_or(Error::Overflow)?; - if max_offset > self.len() * PAGE_SIZE { - return Err(Error::InvalidArgs); - } - - let num_skip_pages = offset / PAGE_SIZE; - let mut start = offset % PAGE_SIZE; - for frame in self.iter().skip(num_skip_pages) { - let read_len = frame - .reader() - .skip(start) - .read_fallible(writer) - .map_err(|(e, _)| e)?; - if read_len == 0 { - break; - } - start = 0; - } - Ok(()) - } - - fn write(&self, offset: usize, reader: &mut VmReader) -> Result<()> { - // Do bound check with potential integer overflow in mind - let max_offset = offset.checked_add(reader.remain()).ok_or(Error::Overflow)?; - if max_offset > self.len() * PAGE_SIZE { - return Err(Error::InvalidArgs); - } - - let num_skip_pages = offset / PAGE_SIZE; - let mut start = offset % PAGE_SIZE; - for frame in self.iter().skip(num_skip_pages) { - let write_len = frame - .writer() - .skip(start) - .write_fallible(reader) - .map_err(|(e, _)| e)?; - if write_len == 0 { - break; - } - start = 0; - } - Ok(()) - } -} - impl PageMeta for FrameMeta { const USAGE: PageUsage = PageUsage::Frame; diff --git a/ostd/src/mm/frame/segment.rs b/ostd/src/mm/frame/segment.rs index 504190d6a..949b4ed9d 100644 --- a/ostd/src/mm/frame/segment.rs +++ b/ostd/src/mm/frame/segment.rs @@ -1,27 +1,33 @@ // SPDX-License-Identifier: MPL-2.0 -//! A contiguous range of page frames. +//! A contiguous segment of untyped memory pages. -use alloc::sync::Arc; use core::ops::Range; -use super::Frame; use crate::{ mm::{ - page::{cont_pages::ContPages, meta::FrameMeta, Page}, - FallibleVmRead, FallibleVmWrite, HasPaddr, Infallible, Paddr, VmIo, VmReader, VmWriter, - PAGE_SIZE, + io::{FallibleVmRead, FallibleVmWrite}, + page::{meta::FrameMeta, ContPages}, + Frame, HasPaddr, Infallible, Paddr, VmIo, VmReader, VmWriter, }, Error, Result, }; -/// A handle to a contiguous range of page frames (physical memory pages). +/// A contiguous segment of untyped memory pages. /// -/// A cloned `Segment` refers to the same page frames as the original. -/// As the original and cloned instances point to the same physical address, -/// they are treated as equal to each other. +/// A [`Segment`] object is a handle to a contiguous range of untyped memory +/// pages, and the underlying pages can be shared among multiple threads. +/// [`Segment::slice`] can be used to clone a slice of the segment (also can be +/// used to clone the entire range). Reference counts are maintained for each +/// page in the segment. So cloning the handle may not be cheap as it +/// increments the reference count of all the cloned pages. /// -/// #Example +/// Other [`Frame`] handles can also refer to the pages in the segment. And +/// the segment can be iterated over to get all the frames in it. +/// +/// To allocate a segment, use [`crate::mm::FrameAllocator`]. +/// +/// # Example /// /// ```rust /// let vm_segment = FrameAllocOptions::new(2) @@ -29,88 +35,102 @@ use crate::{ /// .alloc_contiguous()?; /// vm_segment.write_bytes(0, buf)?; /// ``` -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Segment { - inner: Arc>, - range: Range, + pages: ContPages, } impl HasPaddr for Segment { fn paddr(&self) -> Paddr { - self.start_paddr() + self.pages.start_paddr() + } +} + +impl Clone for Segment { + fn clone(&self) -> Self { + Self { + pages: self.pages.clone(), + } } } impl Segment { - /// Returns a part of the `Segment`. - /// - /// # Panics - /// - /// If `range` is not within the range of this `Segment`, - /// then the method panics. - pub fn range(&self, range: Range) -> Self { - let orig_range = &self.range; - let adj_range = (range.start + orig_range.start)..(range.end + orig_range.start); - assert!(!adj_range.is_empty() && adj_range.end <= orig_range.end); - - Self { - inner: self.inner.clone(), - range: adj_range, - } - } - /// Returns the start physical address. pub fn start_paddr(&self) -> Paddr { - self.start_frame_index() * PAGE_SIZE + self.pages.start_paddr() } /// Returns the end physical address. pub fn end_paddr(&self) -> Paddr { - (self.start_frame_index() + self.nframes()) * PAGE_SIZE + self.pages.end_paddr() } - /// Returns the number of page frames. - pub fn nframes(&self) -> usize { - self.range.len() - } - - /// Returns the number of bytes. + /// Returns the number of bytes in it. pub fn nbytes(&self) -> usize { - self.nframes() * PAGE_SIZE + self.pages.nbytes() } - fn start_frame_index(&self) -> usize { - self.inner.start_paddr() / PAGE_SIZE + self.range.start + /// Split the segment into two at the given byte offset from the start. + /// + /// The resulting segments cannot be empty. So the byte offset cannot be + /// neither zero nor the length of the segment. + /// + /// # Panics + /// + /// The function panics if the byte offset is out of bounds, at either ends, or + /// not base-page-aligned. + pub fn split(self, offset: usize) -> (Self, Self) { + let (left, right) = self.pages.split(offset); + (Self { pages: left }, Self { pages: right }) } - /// Returns a raw pointer to the starting virtual address of the `Segment`. - pub fn as_ptr(&self) -> *const u8 { - super::paddr_to_vaddr(self.start_paddr()) as *const u8 + /// Get an extra handle to the segment in the byte range. + /// + /// The sliced byte range in indexed by the offset from the start of the + /// segment. The resulting segment holds extra reference counts. + /// + /// # Panics + /// + /// The function panics if the byte range is out of bounds, or if any of + /// the ends of the byte range is not base-page aligned. + pub fn slice(&self, range: &Range) -> Self { + Self { + pages: self.pages.slice(range), + } } - /// Returns a mutable raw pointer to the starting virtual address of the `Segment`. - pub fn as_mut_ptr(&self) -> *mut u8 { - super::paddr_to_vaddr(self.start_paddr()) as *mut u8 + /// Gets a [`VmReader`] to read from the segment from the beginning to the end. + pub fn reader(&self) -> VmReader<'_, Infallible> { + let ptr = super::paddr_to_vaddr(self.start_paddr()) as *const u8; + // SAFETY: + // - The memory range points to untyped memory. + // - The segment is alive during the lifetime `'a`. + // - Using `VmReader` and `VmWriter` is the only way to access the segment. + unsafe { VmReader::from_kernel_space(ptr, self.nbytes()) } + } + + /// Gets a [`VmWriter`] to write to the segment from the beginning to the end. + pub fn writer(&self) -> VmWriter<'_, Infallible> { + let ptr = super::paddr_to_vaddr(self.start_paddr()) as *mut u8; + // SAFETY: + // - The memory range points to untyped memory. + // - The segment is alive during the lifetime `'a`. + // - Using `VmReader` and `VmWriter` is the only way to access the segment. + unsafe { VmWriter::from_kernel_space(ptr, self.nbytes()) } } } -impl<'a> Segment { - /// Returns a reader to read data from it. - pub fn reader(&'a self) -> VmReader<'a, Infallible> { - // SAFETY: - // - The memory range points to untyped memory. - // - The segment is alive during the lifetime `'a`. - // - Using `VmReader` and `VmWriter` is the only way to access the segment. - unsafe { VmReader::from_kernel_space(self.as_ptr(), self.nbytes()) } +impl From for Segment { + fn from(frame: Frame) -> Self { + Self { + pages: ContPages::from(frame.page), + } } +} - /// Returns a writer to write data into it. - pub fn writer(&'a self) -> VmWriter<'a, Infallible> { - // SAFETY: - // - The memory range points to untyped memory. - // - The segment is alive during the lifetime `'a`. - // - Using `VmReader` and `VmWriter` is the only way to access the segment. - unsafe { VmWriter::from_kernel_space(self.as_mut_ptr(), self.nbytes()) } +impl From> for Segment { + fn from(pages: ContPages) -> Self { + Self { pages } } } @@ -148,21 +168,10 @@ impl VmIo for Segment { } } -impl From for Segment { - fn from(frame: Frame) -> Self { - Self { - inner: Arc::new(Page::::from(frame).into()), - range: 0..1, - } - } -} +impl Iterator for Segment { + type Item = Frame; -impl From> for Segment { - fn from(cont_pages: ContPages) -> Self { - let len = cont_pages.len(); - Self { - inner: Arc::new(cont_pages), - range: 0..len / PAGE_SIZE, - } + fn next(&mut self) -> Option { + self.pages.next().map(|page| Frame { page }) } } diff --git a/ostd/src/mm/page/cont_pages.rs b/ostd/src/mm/page/cont_pages.rs index 8ef42d669..a6a95ce8e 100644 --- a/ostd/src/mm/page/cont_pages.rs +++ b/ostd/src/mm/page/cont_pages.rs @@ -5,7 +5,7 @@ use alloc::vec::Vec; use core::{mem::ManuallyDrop, ops::Range}; -use super::{meta::PageMeta, Page}; +use super::{inc_page_ref_count, meta::PageMeta, Page}; use crate::mm::{Paddr, PAGE_SIZE}; /// A contiguous range of physical memory pages. @@ -25,16 +25,31 @@ pub struct ContPages { impl Drop for ContPages { fn drop(&mut self) { - for i in self.range.clone().step_by(PAGE_SIZE) { + for paddr in self.range.clone().step_by(PAGE_SIZE) { // SAFETY: for each page there would be a forgotten handle // when creating the `ContPages` object. - drop(unsafe { Page::::from_raw(i) }); + drop(unsafe { Page::::from_raw(paddr) }); + } + } +} + +impl Clone for ContPages { + fn clone(&self) -> Self { + for paddr in self.range.clone().step_by(PAGE_SIZE) { + // SAFETY: for each page there would be a forgotten handle + // when creating the `ContPages` object, so we already have + // reference counts for the pages. + unsafe { inc_page_ref_count(paddr) }; + } + Self { + range: self.range.clone(), + _marker: core::marker::PhantomData, } } } impl ContPages { - /// Create a new `ContPages` from unused pages. + /// Creates a new `ContPages` from unused pages. /// /// The caller must provide a closure to initialize metadata for all the pages. /// The closure receives the physical address of the page and returns the @@ -49,8 +64,8 @@ impl ContPages { where F: FnMut(Paddr) -> M, { - for i in range.clone().step_by(PAGE_SIZE) { - let _ = ManuallyDrop::new(Page::::from_unused(i, metadata_fn(i))); + for paddr in range.clone().step_by(PAGE_SIZE) { + let _ = ManuallyDrop::new(Page::::from_unused(paddr, metadata_fn(paddr))); } Self { range, @@ -58,20 +73,76 @@ impl ContPages { } } - /// Get the start physical address of the contiguous pages. + /// Gets the start physical address of the contiguous pages. pub fn start_paddr(&self) -> Paddr { self.range.start } - /// Get the end physical address of the contiguous pages. + /// Gets the end physical address of the contiguous pages. pub fn end_paddr(&self) -> Paddr { self.range.end } - /// Get the length in bytes of the contiguous pages. - pub fn len(&self) -> usize { + /// Gets the length in bytes of the contiguous pages. + pub fn nbytes(&self) -> usize { self.range.end - self.range.start } + + /// Splits the pages into two at the given byte offset from the start. + /// + /// The resulting pages cannot be empty. So the offset cannot be neither + /// zero nor the length of the pages. + /// + /// # Panics + /// + /// The function panics if the offset is out of bounds, at either ends, or + /// not base-page-aligned. + pub fn split(self, offset: usize) -> (Self, Self) { + assert!(offset % PAGE_SIZE == 0); + assert!(0 < offset && offset < self.nbytes()); + + let old = ManuallyDrop::new(self); + let at = old.range.start + offset; + + ( + Self { + range: old.range.start..at, + _marker: core::marker::PhantomData, + }, + Self { + range: at..old.range.end, + _marker: core::marker::PhantomData, + }, + ) + } + + /// Gets an extra handle to the pages in the byte offset range. + /// + /// The sliced byte offset range in indexed by the offset from the start of + /// the contiguous pages. The resulting pages holds extra reference counts. + /// + /// # Panics + /// + /// The function panics if the byte offset range is out of bounds, or if + /// any of the ends of the byte offset range is not base-page aligned. + pub fn slice(&self, range: &Range) -> Self { + assert!(range.start % PAGE_SIZE == 0 && range.end % PAGE_SIZE == 0); + let start = self.range.start + range.start; + let end = self.range.start + range.end; + assert!(start <= end && end <= self.range.end); + + for paddr in (start..end).step_by(PAGE_SIZE) { + // SAFETY: We already have reference counts for the pages since + // for each page there would be a forgotten handle when creating + // the `ContPages` object. + unsafe { inc_page_ref_count(paddr) }; + } + + Self { + range: start..end, + _marker: core::marker::PhantomData, + } + } } impl From> for ContPages { @@ -100,3 +171,21 @@ impl From> for Vec> { vector } } + +impl Iterator for ContPages { + type Item = Page; + + fn next(&mut self) -> Option { + if self.range.start < self.range.end { + // SAFETY: each page in the range would be a handle forgotten + // when creating the `ContPages` object. + let page = unsafe { Page::::from_raw(self.range.start) }; + self.range.start += PAGE_SIZE; + // The end cannot be non-page-aligned. + debug_assert!(self.range.start <= self.range.end); + Some(page) + } else { + None + } + } +} diff --git a/ostd/src/mm/page/mod.rs b/ostd/src/mm/page/mod.rs index 336199756..ee0057244 100644 --- a/ostd/src/mm/page/mod.rs +++ b/ostd/src/mm/page/mod.rs @@ -15,7 +15,7 @@ //! the handle only a pointer to the metadata. pub mod allocator; -pub mod cont_pages; +mod cont_pages; pub mod meta; use core::{