Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Closes #241] Use Pin API for unmovable structs #356

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions kernel-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions kernel-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ spin = { version = "0.7.0", default-features = false }
array-macro = "2.0.0"
static_assertions = "1.1.0"
itertools = { version = "0.9.0", default-features = false }
pin-project = "1.0.4"

# Compiler options for sysroot packages.
# Cargo currently warns following packages are not dependencies.
Expand Down
12 changes: 10 additions & 2 deletions kernel-rs/src/bio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ use crate::{

use core::mem;
use core::ops::{Deref, DerefMut};
use core::pin::Pin;

pub struct BufEntry {
dev: u32,
pub blockno: u32,

/// WaitChannel saying virtio_disk request is done.
pub vdisk_request_waitchannel: WaitChannel,
vdisk_request_waitchannel: WaitChannel,

pub inner: Sleeplock<BufInner>,
}
Expand All @@ -41,6 +42,13 @@ impl BufEntry {
inner: Sleeplock::new("buffer", BufInner::zero()),
}
}

/// Returns the `WaitChannel` saying virtio_disk request is done.
pub fn get_waitchannel(&self) -> Pin<&WaitChannel> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 이게 pin을 리턴함으로써 어떤 장점이 생기는지 알 수 있을까요?
    • &self가 Pin이어야할 필요는 없을까요?

Copy link
Collaborator Author

@travis1829 travis1829 Jan 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • WaitChannel::sleep() 함수의 signature를 sleep(&self, ...) -> sleep(self: Pin<&Self>, ...)로 바꿨기에 어차피 언젠가는 Pin을 만들어야 하기 때문에, getter 함수가 그냥 처음부터 Pin을 리턴하도록 했습니다.

&self가 Pin이어야할 필요는 없을까요?

// TODO: change `&self` -> `self: Pin<&Self>`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 이 todo는 어떻게 해결 가능한가요?
  • 메소드 이름에 vdisk_request 이 들어가는게 나을지도 모르겠습니다. 어떻게 보시나요? 일반적으로 이 채널이 어떤건지 30번줄에 주석이 좀더 있으면 좋겠습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • &self -> self: Pin<&Self>로 바꾼 후, virtio_disk.rs에서 Buf을 access할때는 항상 Pin<&...>형태로 access하도록 바꿔야 할텐데, 바꿔야할 부분이 많아서 일단은 TODO로만 표시했습니다.
  • 맞는 것 같습니다!

// and obtain a pin from `self` instead of making a new pin with `new_unchecked()`.
unsafe { Pin::new_unchecked(&self.vdisk_request_waitchannel) }
}
}

impl ArenaObject for BufEntry {
Expand Down Expand Up @@ -145,7 +153,7 @@ impl Bcache {
)
}

/// Return a unlocked buf with the contents of the indicated block.
/// Returns an unlocked buf with the contents of the indicated block.
pub fn get_buf(&self, dev: u32, blockno: u32) -> BufUnlocked<'_> {
let inner = self
.find_or_alloc(
Expand Down
6 changes: 3 additions & 3 deletions kernel-rs/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ impl File {
}

match &self.typ {
FileType::Pipe { pipe } => pipe.read(addr, usize::try_from(n).unwrap_or(0)),
FileType::Pipe { pipe } => pipe.inner().read(addr, usize::try_from(n).unwrap_or(0)),
FileType::Inode { ip, off } => {
let mut ip = ip.deref().lock();
let curr_off = *off.get();
Expand All @@ -122,7 +122,7 @@ impl File {
}

match &self.typ {
FileType::Pipe { pipe } => pipe.write(addr, usize::try_from(n).unwrap_or(0)),
FileType::Pipe { pipe } => pipe.inner().write(addr, usize::try_from(n).unwrap_or(0)),
FileType::Inode { ip, off } => {
// write a few blocks at a time to avoid exceeding
// the maximum log transaction size, including
Expand Down Expand Up @@ -178,7 +178,7 @@ impl ArenaObject for File {
A::reacquire_after(guard, || {
let typ = mem::replace(&mut self.typ, FileType::None);
match typ {
FileType::Pipe { mut pipe } => unsafe { pipe.close(self.writable) },
FileType::Pipe { pipe } => pipe.inner().close(self.writable),
FileType::Inode { ip, .. } | FileType::Device { ip, .. } => {
// TODO(rv6)
// The inode ip will be dropped by drop(ip). Deallocation
Expand Down
2 changes: 0 additions & 2 deletions kernel-rs/src/kernel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ pub struct Kernel {

/// Memory for virtio descriptors `&c` for queue 0.
///
/// This is a global instead of allocated because it must be multiple contiguous pages, which
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 주석은 왜 사라지는지 궁금합니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eadf240 에서 이미 pub virtque -> virtque로 바꿨는데 주석은 그대로 놔둔었길래 주석만 없앴습니다. (즉, 이 PR에서의 변경사항과는 사실 관련 없습니다.)

/// `kernel().alloc()` doesn't support, and page aligned.
// TODO(efenniht): I moved out pages from Disk. Did I changed semantics (pointer indirection?)
virtqueue: [RawPage; 2],

Expand Down
2 changes: 2 additions & 0 deletions kernel-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,5 @@ extern crate array_macro;
extern crate static_assertions;
#[macro_use]
extern crate itertools;
#[macro_use]
extern crate pin_project;
83 changes: 49 additions & 34 deletions kernel-rs/src/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
spinlock::Spinlock,
vm::UVAddr,
};
use core::{mem, ops::Deref, ptr};
use core::{mem, pin::Pin, ptr};

const PIPESIZE: usize = 512;

Expand All @@ -27,32 +27,36 @@ struct PipeInner {
writeopen: bool,
}

#[pin_project]
pub struct Pipe {
inner: Spinlock<PipeInner>,

/// WaitChannel for saying there are unread bytes in Pipe.data.
#[pin]
read_waitchannel: WaitChannel,

/// WaitChannel for saying all bytes in Pipe.data are already read.
#[pin]
write_waitchannel: WaitChannel,
}

impl Pipe {
/// PipeInner::try_read() tries to read as much as possible.
/// Pipe::read() executes try_read() until all bytes in pipe are read.
//TODO : `n` should be u32
pub unsafe fn read(&self, addr: UVAddr, n: usize) -> Result<usize, ()> {
let mut inner = self.inner.lock();
pub unsafe fn read(self: Pin<&Self>, addr: UVAddr, n: usize) -> Result<usize, ()> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&self는 어차피 move가 안되는데 pin으로 감싸야할 이유가 있나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실 safety랑은 상관이 없고, Pipe을 쓸때는 항상 Pin을 한 후에만 사용하라는 뜻입니다. Pin을 한 후에 사용해야 하는 struct는 method들이 항상 self:Pin<&...>형태인 것이 관례인 것 같습니다.

https://rust-lang.github.io/async-book/04_pinning/01_chapter.html

use std::pin::Pin;
use std::marker::PhantomPinned;

#[derive(Debug)]
struct Test {
    a: String,
    b: *const String,
    _marker: PhantomPinned,
}


impl Test {
    fn new(txt: &str) -> Self {
        Test {
            a: String::from(txt),
            b: std::ptr::null(),
            _marker: PhantomPinned, // This makes our type `!Unpin`
        }
    }
    fn init<'a>(self: Pin<&'a mut Self>) {
        let self_ptr: *const String = &self.a;
        let this = unsafe { self.get_unchecked_mut() };
        this.b = self_ptr;
    }

    fn a<'a>(self: Pin<&'a Self>) -> &'a str {
        &self.get_ref().a
    }

    fn b<'a>(self: Pin<&'a Self>) -> &'a String {
        assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
        unsafe { &*(self.b) }
    }
}

let this = self.project_ref();
let mut inner = this.inner.lock();
loop {
match inner.try_read(addr, n) {
Ok(r) => {
//DOC: piperead-wakeup
self.write_waitchannel.wakeup();
this.write_waitchannel.wakeup();
return Ok(r);
}
Err(PipeError::WaitForIO) => {
//DOC: piperead-sleep
self.read_waitchannel.sleep(&mut inner);
this.read_waitchannel.sleep(&mut inner);
}
_ => return Err(()),
}
Expand All @@ -61,64 +65,63 @@ impl Pipe {

/// PipeInner::try_write() tries to write as much as possible.
/// Pipe::write() executes try_write() until `n` bytes are written.
pub unsafe fn write(&self, addr: UVAddr, n: usize) -> Result<usize, ()> {
pub unsafe fn write(self: Pin<&Self>, addr: UVAddr, n: usize) -> Result<usize, ()> {
let mut written = 0;
let mut inner = self.inner.lock();
let this = self.project_ref();
let mut inner = this.inner.lock();
loop {
match inner.try_write(addr + written, n - written) {
Ok(r) => {
written += r;
self.read_waitchannel.wakeup();
this.read_waitchannel.wakeup();
if written < n {
self.write_waitchannel.sleep(&mut inner);
this.write_waitchannel.sleep(&mut inner);
} else {
return Ok(written);
}
}
Err(PipeError::InvalidCopyin(i)) => {
self.read_waitchannel.wakeup();
this.read_waitchannel.wakeup();
return Ok(written + i);
}
_ => return Err(()),
}
}
}

unsafe fn close(&self, writable: bool) -> bool {
let mut inner = self.inner.lock();
pub fn close(self: Pin<&Self>, writable: bool) {
let this = self.project_ref();
let mut inner = this.inner.lock();

if writable {
inner.writeopen = false;
self.read_waitchannel.wakeup();
this.read_waitchannel.wakeup();
} else {
inner.readopen = false;
self.write_waitchannel.wakeup();
this.write_waitchannel.wakeup();
}
}

fn closed(self: Pin<&Self>) -> bool {
let this = self.project_ref();
let inner = this.inner.lock();

// Return whether pipe would be freed or not
!inner.readopen && !inner.writeopen
}
}

// TODO: Remove Copy and Clone
#[derive(Copy, Clone)]
pub struct AllocatedPipe {
ptr: *mut Pipe,
}

impl Deref for AllocatedPipe {
type Target = Pipe;
fn deref(&self) -> &Self::Target {
unsafe { &*self.ptr }
}
// `Pipe` must not be movable, since `WaitChannel` must not be movable.
pin: Pin<&'static Pipe>,
}

impl AllocatedPipe {
pub fn alloc() -> Result<(RcFile<'static>, RcFile<'static>), ()> {
let page = kernel().alloc().ok_or(())?;
let ptr = page.into_usize() as *mut Pipe;

// `Pipe` must be align with `Page`
// `Pipe` must be aligned with `Page`.
const_assert!(mem::size_of::<Pipe>() <= PGSIZE);

//TODO(rv6): Since Pipe is a huge struct, need to check whether stack is used to fill `*ptr`
Expand All @@ -143,27 +146,39 @@ impl AllocatedPipe {
},
);
};
let pin = unsafe {
// Safe because after this `alloc()` function ends, we can access the `Pipe`
// only through `AllocatedPipe::pin`, which is `Pin<&Pipe>` type.
// Hence, the `Pipe` cannot be moved.
Pin::new_unchecked(&*ptr)
};
let f0 = kernel()
.ftable
.alloc_file(FileType::Pipe { pipe: Self { ptr } }, true, false)
.alloc_file(FileType::Pipe { pipe: Self { pin } }, true, false)
// It is safe because ptr is an address of page, which obtained by alloc()
.map_err(|_| kernel().free(unsafe { Page::from_usize(ptr as _) }))?;
let f1 = kernel()
.ftable
.alloc_file(FileType::Pipe { pipe: Self { ptr } }, false, true)
.alloc_file(FileType::Pipe { pipe: Self { pin } }, false, true)
// It is safe because ptr is an address of page, which obtained by alloc()
.map_err(|_| kernel().free(unsafe { Page::from_usize(ptr as _) }))?;

Ok((f0, f1))
}

// TODO: use `Drop` instead of `close`
// TODO: use `self` instead of `&mut self`
// `&mut self` is used because `Drop` of `File` uses AllocatedPipe inside File.
// https://github.com/kaist-cp/rv6/pull/211#discussion_r491671723
pub unsafe fn close(&mut self, writable: bool) {
if (*self.ptr).close(writable) {
kernel().free(Page::from_usize(self.ptr as *mut Pipe as _));
pub fn inner(&self) -> Pin<&Pipe> {
self.pin
}
}

impl Drop for AllocatedPipe {
fn drop(&mut self) {
if self.pin.closed() {
let ptr = self.pin.get_ref() as *const Pipe;
unsafe {
// Safe since this is a page that was allocated through `kernel().alloc()`.
kernel().free(Page::from_usize(ptr as _));
}
}
}
}
Expand Down
10 changes: 7 additions & 3 deletions kernel-rs/src/proc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use core::{
cmp,
mem::{self, MaybeUninit},
ops::{Deref, DerefMut},
pin::Pin,
ptr, slice, str,
sync::atomic::{AtomicBool, AtomicI32, Ordering},
};
Expand Down Expand Up @@ -240,7 +241,7 @@ impl WaitChannel {

/// Atomically release lock and sleep on waitchannel.
/// Reacquires lock when awakened.
pub fn sleep<T: Waitable>(&self, lk: &mut T) {
pub fn sleep<T: Waitable>(self: Pin<&Self>, lk: &mut T) {
let p = unsafe {
// TODO: Remove this unsafe part after resolving #258
&*myproc()
Expand All @@ -263,7 +264,7 @@ impl WaitChannel {
}

// Go to sleep.
guard.deref_mut_info().waitchannel = self;
guard.deref_mut_info().waitchannel = self.get_ref();
guard.deref_mut_info().state = Procstate::SLEEPING;
unsafe {
// Safe since we hold `p.lock()`, changed the process's state,
Expand All @@ -284,6 +285,7 @@ impl WaitChannel {

/// Wake up all processes sleeping on waitchannel.
/// Must be called without any p->lock.
/// TODO: `&self` -> `self: Pin<&Self>`.
pub fn wakeup(&self) {
kernel().procs.wakeup_pool(self)
}
Expand Down Expand Up @@ -817,7 +819,9 @@ impl ProcessSystem {

// Wait for a child to exit.
//DOC: wait-sleep
((*p).info.get_mut_unchecked().child_waitchannel).sleep(&mut parent_guard);
// TODO: Obtain a pin with `project_ref()` instead of making a new pin with `new_unchecked()`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 todo는 해결하기 어렵나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 이 부분은 Spinlock이랑 관련이 있는데, 현재 Spinlock::get_mut_unchecked()&mut T를 리턴합니다. 그런데, T가 move 되서는 안되는 type인 경우 (ex: 위의 예에서는 ProcInfo) 어떠한 방법으로든 &mut T를 얻을 수 있어선 안됩니다. 그래서, 이 부분은 Proc으로부터 Pin<&mut ProcInfo>를 얻을 수 있는 새로운 method을 추가함으로써 해결해야할 것 같습니다.
  • 다만, 이것 말고도 move되선 안되는 T에 대해서 Spinlock::get_mut_unchecked()를 쓰는 경우가 많은 것 같은데, 차라리 Spinlock의 API를 바꾸는게 나을지 (ex: T가 move되도 괜찮다면 &mut T를 리턴하고 그렇지 않으면 Pin<&mut T>를 리턴하도록) 고민중이어서 TODO로 표시해놨습니다.

let waitchannel = Pin::new_unchecked(&(*p).info.get_mut_unchecked().child_waitchannel);
waitchannel.sleep(&mut parent_guard);
}
}

Expand Down
Loading