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

Multipage Deallocator #19

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 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
54 changes: 54 additions & 0 deletions evaluation/tests/large_remove.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include <fcntl.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdio.h>
#include <assert.h>
#include <sys/statvfs.h>
#define PAGESZ 4096
// consume all the pages and test effectiveness of the deallocator to make space for a new file
// extend the size of a file or create a file and return the new size of the file
long int enlarge_file(char *path, long int size) {
int fd = open(path, O_CREAT | O_RDWR);
assert (fd > -1);
lseek(fd, size, SEEK_SET);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this actually increase the file's size? According to lseek documentation (https://man7.org/linux/man-pages/man2/lseek.2.html),

lseek() allows the file offset to be set beyond the end of the file (but this does not change the size of the file).

Edit: I see, the increase comes from the fputc writing a character at offset size. I would still suggest switching to one of the following approaches.

A more standard way of increasing the file's size (and one that would be sure to exercise page allocation code paths) would be to use the write system call to write a specified amount of data to the file: https://man7.org/linux/man-pages/man2/write.2.html, or the truncate syscall to set its size to a specified value. It doesn't actually matter what is written, so you could allocate a buffer but not update/set any of its bytes and just write whatever is already in it to the file. You can also do this with a FILE* obtained from fopen/fdopen using a function like fprintf, but the write system call may be easier because I think fprintf requires you to provide bytes to write as a string.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Great suggestion. I added a write call and it was much cleaner.

FILE *fp = fdopen(fd, "w");
assert(fp);
fputc('\0', fp);
fclose(fp);
close(fd);
fd = open(path, O_CREAT | O_RDWR);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why close and reopen the file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You are write. I over complicated the code with lseek and changed to a write call.

assert (fd > -1);
lseek(fd, 0L, SEEK_END);
long int new_size = lseek(fd, 0, SEEK_CUR) - 1;
close(fd);
return new_size;
}

int main(void) {
struct statvfs stat;
assert(statvfs("/mnt/pmem", &stat) == 0);
unsigned long pages_start = stat.f_bfree;
bool used_all_pages = false;
int multiple = 1;
long int prev_size = 0;
char *path = "/mnt/pmem/myfile";
char *path2 = "/mnt/pmem/myfile2";
while (!used_all_pages) {
long int new_size = enlarge_file(path, multiple * PAGESZ);
used_all_pages = prev_size == new_size;
multiple = used_all_pages ? multiple : multiple + 1;
prev_size = new_size;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure I understand exactly what this loop is doing; why increase multiple in this way?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The loop was intended to extend the file to use the maximum amount of pages before we run out, but that was overcomplicated. I now borrow the code from large_write and allocate 200000 pages instead.

}
// a new file with one less page should be able to be allocated after removal
assert(prev_size > 0);
Copy link
Collaborator

Choose a reason for hiding this comment

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

A stronger check would be to assert that the file you created is the expected size.

assert (remove(path) == 0);

assert(statvfs("/mnt/pmem", &stat) == 0);
unsigned long pages_end = stat.f_bfree;
assert(pages_start == pages_end);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Another useful sanity check would be to ensure that between creating and deleting the big file, the number of pages in use has changed.


long int new_size = enlarge_file(path2, PAGESZ * (multiple - 1));
assert (remove(path2) == 0);
assert(new_size == PAGESZ * (multiple - 1));
return 0;
}
37 changes: 37 additions & 0 deletions evaluation/tests/remove_multiple_files.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <fcntl.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <sys/statvfs.h>
#define PAGESZ 4096

// create many files and test whether removing will return all pages to free list
int main(void) {
struct statvfs stat;
assert(statvfs("/mnt/pmem", &stat) == 0);
unsigned long pages_start = stat.f_bfree;
unsigned num_files = 5000;
char filename[64];
memset(filename, 0, 64);
for (int i = 0; i < num_files; i ++) {
sprintf(filename, "/mnt/pmem/%d", i);
int fd = open(filename, O_CREAT | O_RDWR);
lseek(fd, PAGESZ * 2, SEEK_SET);
FILE *fp = fdopen(fd, "w");
assert(fp);
putc('\0', fp);
fclose(fp);
close(fd);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same comment as the other test w.r.t. increasing file size.


for (int i = 0; i < num_files; i ++) {
sprintf(filename, "/mnt/pmem/%d", i);
assert(remove(filename) == 0);
}
assert(statvfs("/mnt/pmem", &stat) == 0);
unsigned long pages_end = stat.f_bfree;
assert(pages_start == pages_end);
return 0;
}
78 changes: 69 additions & 9 deletions linux/fs/squirrelfs/balloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,23 +261,39 @@ impl PageAllocator for Option<PerCpuPageAllocator> {
fn dealloc_data_page_list(&self, pages: &DataPageListWrapper<Clean, Free>) -> Result<()> {
if let Some(allocator) = self {
let mut page_list = pages.get_page_list_cursor();
let mut page = page_list.current();

// RBTree to store the free list for every cpu
let mut cpu_free_list_map : RBTree<usize, Vec<PageNum>> = RBTree::new();

let mut page = page_list.current(); // head of page list

// get a list of pages #s for each cpu
while page.is_some() {
// janky syntax to deal with the fact that page_list.current() returns an Option
if let Some(page) = page {
// TODO: refactor to avoid acquiring lock on every iteration
allocator.dealloc_page(page.get_page_no())?;
let cpu : usize = allocator.pageno2cpuid(page.get_page_no())?;

// add cpu page to vector (vector is mutable)
let cpu_page_vec : Option<&mut Vec<PageNum>> = cpu_free_list_map.get_mut(&cpu);

if let Some(cpu_page_vec) = cpu_page_vec {
cpu_page_vec.try_push(page.get_page_no())?;
} else {
let mut free_list : Vec<PageNum> = Vec::new();
free_list.try_push(page.get_page_no())?;
cpu_free_list_map.try_insert(cpu, free_list)?;
}

page_list.move_next();
} else {
unreachable!()
}
page = page_list.current();
page = page_list.current();
}
allocator.dealloc_multiple_page(cpu_free_list_map)?;
Ok(())

} else {
pr_info!("ERROR: page allocator is uninitialized\n");
Err(EINVAL)
}
}
}

fn dealloc_dir_page<'a>(&self, page: &DirPageWrapper<'a, Clean, Dealloc>) -> Result<()> {
Expand Down Expand Up @@ -343,6 +359,50 @@ impl PerCpuPageAllocator {
Ok(())
}
}


fn dealloc_multiple_page(&self, cpu_free_list_map : RBTree<usize, Vec<PageNum>>) -> Result<()> {
for (cpu, page_nos) in cpu_free_list_map.iter() {

let free_list = Arc::clone(&self.free_lists[*cpu]);
let mut free_list = free_list.lock();

for page_no in page_nos.iter() {
free_list.free_pages += 1;
let res = free_list.list.try_insert(*page_no, ());

// unwrap the error so we can get at the option
let res = match res {
Ok(res) => res,
Err(e) => {
pr_info!(
"ERROR: failed to insert {:?} into page allocator at CPU {:?}, error {:?}\n",
page_no,
cpu,
e
);
return Err(e);
}
};

if res.is_some() {
pr_info!(
"ERROR: page {:?} was already in the allocator at CPU {:?}\n",
page_no,
cpu
);
return Err(EINVAL);
}
}
}
Ok(())
}

fn pageno2cpuid(&self, page_no : PageNum) -> Result<usize> {
let cpu: usize = ((page_no - self.start) / self.pages_per_cpu).try_into()?;
Ok(cpu)
}

}

// placeholder page descriptor that can represent either a dir or data page descriptor
Expand Down Expand Up @@ -2365,4 +2425,4 @@ impl<Op> DataPageListWrapper<InFlight, Op> {
pages: self.pages,
}
}
}
}