mirror of
https://github.com/zinchenko291/os-course.git
synced 2026-09-12 20:09:58 +05:00
Merge 3b0ea469ff into 2636aef752
This commit is contained in:
commit
8426118809
@ -1,4 +1,4 @@
|
||||
name: Test VT Shell
|
||||
name: Test VT Page Cache
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -6,7 +6,7 @@ on:
|
||||
- main
|
||||
pull_request:
|
||||
paths:
|
||||
- 'lab/vtsh/**'
|
||||
- 'lab/vtpc/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -15,12 +15,12 @@ jobs:
|
||||
cmake_build_type:
|
||||
- Asan
|
||||
- Release
|
||||
runs-on: self-hosted
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: silkeh/clang:latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./lab/vtsh
|
||||
working-directory: ./lab/vtpc
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@ -44,6 +44,11 @@ jobs:
|
||||
\( -iname '*.c' -o -iname '*.h' -o -iname '*.cpp' -o -iname '*.hpp' \) -exec \
|
||||
clang-tidy -p ./build {} \;
|
||||
|
||||
- name: Test
|
||||
working-directory: ./lab/vtsh/test
|
||||
run: python3 -m unittest discover
|
||||
- name: Test Basic
|
||||
run: ./build/test/test_basic
|
||||
|
||||
- name: Test Sequential
|
||||
run: ./build/test/test_seq
|
||||
|
||||
- name: Test Random
|
||||
run: ./build/test/test_random
|
||||
@ -7,6 +7,7 @@ project(
|
||||
)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
add_compile_definitions(_GNU_SOURCE)
|
||||
|
||||
add_subdirectory(lib)
|
||||
add_subdirectory(test)
|
||||
|
||||
@ -2,6 +2,9 @@ add_library(
|
||||
vtpc
|
||||
STATIC
|
||||
vtpc.c
|
||||
vtpc_state.c
|
||||
vtpc_list.c
|
||||
vtpc_cache.c
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
|
||||
@ -1,30 +1,236 @@
|
||||
#include "vtpc.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "vtpc_internal.h"
|
||||
|
||||
int vtpc_open(const char* path, int mode, int access) {
|
||||
return open(path, mode, access);
|
||||
if (vtpc_state_init() < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int open_mode = mode | O_DIRECT;
|
||||
int os_fd = open(path, open_mode, access);
|
||||
if (os_fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int slot = -1;
|
||||
for (int i = 0; i < VTPC_MAX_OPEN_FILES; ++i) {
|
||||
if (!g_state.files[i].used) {
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot < 0) {
|
||||
(void)close(os_fd);
|
||||
errno = EMFILE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (fstat(os_fd, &st) < 0) {
|
||||
int saved = errno;
|
||||
(void)close(os_fd);
|
||||
errno = saved;
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_state.files[slot].used = 1;
|
||||
g_state.files[slot].os_fd = os_fd;
|
||||
g_state.files[slot].open_flags = mode;
|
||||
g_state.files[slot].pos = 0;
|
||||
g_state.files[slot].size = st.st_size;
|
||||
g_state.files[slot].size_dirty = 0;
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
int vtpc_close(int fd) {
|
||||
return close(fd);
|
||||
if (vtpc_state_init() < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
vtpc_file* file = vtpc_get_file(fd);
|
||||
if (file == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int status = 0;
|
||||
int saved_errno = 0;
|
||||
if (vtpc_flush_file_pages(file, fd, 1) < 0) {
|
||||
status = -1;
|
||||
saved_errno = errno;
|
||||
}
|
||||
|
||||
vtpc_drop_file_pages(fd);
|
||||
int os_fd = file->os_fd;
|
||||
file->used = 0;
|
||||
|
||||
if (close(os_fd) < 0 && status == 0) {
|
||||
status = -1;
|
||||
saved_errno = errno;
|
||||
}
|
||||
|
||||
if (status < 0) {
|
||||
errno = saved_errno;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
ssize_t vtpc_read(int fd, void* buf, size_t count) {
|
||||
return read(fd, buf, count);
|
||||
if (vtpc_state_init() < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
vtpc_file* file = vtpc_get_file(fd);
|
||||
if (file == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// O_ACCMODE extracts access bits from open flags.
|
||||
int acc_mode = file->open_flags & O_ACCMODE;
|
||||
if (!(acc_mode == O_RDONLY || acc_mode == O_RDWR)) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (file->pos >= file->size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t total = 0;
|
||||
unsigned char* out = (unsigned char*)buf;
|
||||
while (total < count && file->pos < file->size) {
|
||||
off_t page_no = file->pos / (off_t)g_state.page_size;
|
||||
size_t page_off = (size_t)(file->pos % (off_t)g_state.page_size);
|
||||
size_t chunk = vtpc_min_size(count - total, g_state.page_size - page_off);
|
||||
off_t available = file->size - file->pos;
|
||||
if ((off_t)chunk > available) {
|
||||
chunk = (size_t)available;
|
||||
}
|
||||
|
||||
int page_idx = -1;
|
||||
if (vtpc_get_or_load_page(fd, page_no, 1, &page_idx) < 0) {
|
||||
if (total > 0) {
|
||||
return (ssize_t)total;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(out + total, g_state.resident[page_idx].data + page_off, chunk);
|
||||
total += chunk;
|
||||
file->pos += (off_t)chunk;
|
||||
}
|
||||
|
||||
return (ssize_t)total;
|
||||
}
|
||||
|
||||
ssize_t vtpc_write(int fd, const void* buf, size_t count) {
|
||||
return write(fd, buf, count);
|
||||
if (vtpc_state_init() < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
vtpc_file* file = vtpc_get_file(fd);
|
||||
if (file == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int acc_mode = file->open_flags & O_ACCMODE;
|
||||
if (!(acc_mode == O_WRONLY || acc_mode == O_RDWR)) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((file->open_flags & O_APPEND) != 0) {
|
||||
file->pos = file->size;
|
||||
}
|
||||
|
||||
size_t total = 0;
|
||||
const unsigned char* in = (const unsigned char*)buf;
|
||||
while (total < count) {
|
||||
off_t page_no = file->pos / (off_t)g_state.page_size;
|
||||
size_t page_off = (size_t)(file->pos % (off_t)g_state.page_size);
|
||||
size_t chunk = vtpc_min_size(count - total, g_state.page_size - page_off);
|
||||
|
||||
int need_existing = !(page_off == 0 && chunk == g_state.page_size);
|
||||
int page_idx = -1;
|
||||
if (vtpc_get_or_load_page(fd, page_no, need_existing, &page_idx) < 0) {
|
||||
if (total > 0) {
|
||||
return (ssize_t)total;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(g_state.resident[page_idx].data + page_off, in + total, chunk);
|
||||
g_state.resident[page_idx].dirty = 1;
|
||||
|
||||
total += chunk;
|
||||
file->pos += (off_t)chunk;
|
||||
if (file->pos > file->size) {
|
||||
file->size = file->pos;
|
||||
file->size_dirty = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return (ssize_t)total;
|
||||
}
|
||||
|
||||
off_t vtpc_lseek(int fd, off_t offset, int whence) {
|
||||
return lseek(fd, offset, whence);
|
||||
if (vtpc_state_init() < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
vtpc_file* file = vtpc_get_file(fd);
|
||||
if (file == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// whence defines the seek base: start, current position, or end.
|
||||
off_t base = 0;
|
||||
if (whence == SEEK_SET) {
|
||||
base = 0;
|
||||
} else if (whence == SEEK_CUR) {
|
||||
base = file->pos;
|
||||
} else if (whence == SEEK_END) {
|
||||
base = file->size;
|
||||
} else {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
off_t next = base + offset;
|
||||
if (next < 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
file->pos = next;
|
||||
return next;
|
||||
}
|
||||
|
||||
int vtpc_fsync(int fd) {
|
||||
return fsync(fd);
|
||||
if (vtpc_state_init() < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
vtpc_file* file = vtpc_get_file(fd);
|
||||
if (file == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return vtpc_flush_file_pages(file, fd, 1);
|
||||
}
|
||||
|
||||
320
lab/vtpc/lib/vtpc_cache.c
Normal file
320
lab/vtpc/lib/vtpc_cache.c
Normal file
@ -0,0 +1,320 @@
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "vtpc_internal.h"
|
||||
|
||||
int vtpc_find_resident_page(int file_slot, off_t page_no) {
|
||||
for (int i = 0; i < VTPC_MAX_CACHE_PAGES; ++i) {
|
||||
vtpc_resident_page* page = &g_state.resident[i];
|
||||
if (page->used && page->file_slot == file_slot &&
|
||||
page->page_no == page_no) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int vtpc_find_ghost_page(int file_slot, off_t page_no) {
|
||||
for (int i = 0; i < VTPC_MAX_A1OUT_PAGES; ++i) {
|
||||
vtpc_ghost_page* page = &g_state.ghosts[i];
|
||||
if (page->used && page->file_slot == file_slot &&
|
||||
page->page_no == page_no) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int vtpc_alloc_resident_slot(void) {
|
||||
for (int i = 0; i < VTPC_MAX_CACHE_PAGES; ++i) {
|
||||
vtpc_resident_page* page = &g_state.resident[i];
|
||||
if (!page->used) {
|
||||
if (page->data == NULL) {
|
||||
// O_DIRECT requires aligned buffers.
|
||||
if (posix_memalign(
|
||||
(void**)&page->data, g_state.page_size, g_state.page_size
|
||||
) != 0) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
page->used = 1;
|
||||
page->prev = -1;
|
||||
page->next = -1;
|
||||
page->list = VTPC_LIST_NONE;
|
||||
page->dirty = 0;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int vtpc_alloc_ghost_slot(void) {
|
||||
for (int i = 0; i < VTPC_MAX_A1OUT_PAGES; ++i) {
|
||||
if (!g_state.ghosts[i].used) {
|
||||
g_state.ghosts[i].used = 1;
|
||||
g_state.ghosts[i].prev = -1;
|
||||
g_state.ghosts[i].next = -1;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int vtpc_pread_page(int os_fd, off_t page_no, unsigned char* data) {
|
||||
const off_t offset = (off_t)(page_no * (off_t)g_state.page_size);
|
||||
size_t done = 0;
|
||||
while (done < g_state.page_size) {
|
||||
ssize_t rc = pread(
|
||||
os_fd, data + done, g_state.page_size - done, offset + (off_t)done
|
||||
);
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (rc == 0) {
|
||||
memset(data + done, 0, g_state.page_size - done);
|
||||
break;
|
||||
}
|
||||
done += (size_t)rc;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vtpc_pwrite_page(int os_fd, off_t page_no, const unsigned char* data) {
|
||||
const off_t offset = (off_t)(page_no * (off_t)g_state.page_size);
|
||||
size_t done = 0;
|
||||
while (done < g_state.page_size) {
|
||||
ssize_t rc = pwrite(
|
||||
os_fd, data + done, g_state.page_size - done, offset + (off_t)done
|
||||
);
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (rc == 0) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
done += (size_t)rc;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vtpc_flush_page(int idx) {
|
||||
vtpc_resident_page* page = &g_state.resident[idx];
|
||||
if (!page->used || !page->dirty) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
vtpc_file* file = &g_state.files[page->file_slot];
|
||||
if (!file->used) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (vtpc_pwrite_page(file->os_fd, page->page_no, page->data) < 0) {
|
||||
return -1;
|
||||
}
|
||||
page->dirty = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void vtpc_trim_a1out(void) {
|
||||
while (g_state.a1out.size > g_state.max_a1out) {
|
||||
int idx = vtpc_ghost_list_pop_front(&g_state.a1out);
|
||||
if (idx == -1) {
|
||||
break;
|
||||
}
|
||||
g_state.ghosts[idx].used = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Evict one page by 2Q policy and preserve metadata in A1out when needed.
|
||||
int vtpc_evict_one(void) {
|
||||
int victim = -1;
|
||||
int victim_from_a1in = 0;
|
||||
|
||||
if (g_state.a1in.size > g_state.kin) {
|
||||
victim = vtpc_resident_list_pop_front(&g_state.a1in);
|
||||
victim_from_a1in = 1;
|
||||
} else if (g_state.am.size > 0) {
|
||||
victim = vtpc_resident_list_pop_front(&g_state.am);
|
||||
} else {
|
||||
victim = vtpc_resident_list_pop_front(&g_state.a1in);
|
||||
victim_from_a1in = 1;
|
||||
}
|
||||
|
||||
if (victim == -1) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
|
||||
vtpc_resident_page* page = &g_state.resident[victim];
|
||||
page->list = VTPC_LIST_NONE;
|
||||
|
||||
if (vtpc_flush_page(victim) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (victim_from_a1in) {
|
||||
int ghost_idx = vtpc_alloc_ghost_slot();
|
||||
if (ghost_idx >= 0) {
|
||||
g_state.ghosts[ghost_idx].file_slot = page->file_slot;
|
||||
g_state.ghosts[ghost_idx].page_no = page->page_no;
|
||||
vtpc_ghost_list_push_back(&g_state.a1out, ghost_idx);
|
||||
vtpc_trim_a1out();
|
||||
}
|
||||
}
|
||||
|
||||
page->used = 0;
|
||||
page->dirty = 0;
|
||||
page->file_slot = -1;
|
||||
page->page_no = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vtpc_ensure_resident_capacity(void) {
|
||||
while (g_state.a1in.size + g_state.am.size >= g_state.max_resident) {
|
||||
if (vtpc_evict_one() < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void vtpc_move_resident_to_am(int idx) {
|
||||
vtpc_resident_page* page = &g_state.resident[idx];
|
||||
if (page->list == VTPC_LIST_A1IN) {
|
||||
vtpc_resident_list_remove(&g_state.a1in, idx);
|
||||
} else if (page->list == VTPC_LIST_AM) {
|
||||
vtpc_resident_list_remove(&g_state.am, idx);
|
||||
}
|
||||
page->list = VTPC_LIST_AM;
|
||||
vtpc_resident_list_push_back(&g_state.am, idx);
|
||||
}
|
||||
|
||||
int vtpc_get_or_load_page(
|
||||
int file_slot, off_t page_no, int need_existing, int* out_idx
|
||||
) {
|
||||
++g_state.cache_all;
|
||||
|
||||
int resident_idx = vtpc_find_resident_page(file_slot, page_no);
|
||||
if (resident_idx >= 0) {
|
||||
++g_state.cache_hit;
|
||||
if (g_state.resident[resident_idx].list == VTPC_LIST_A1IN) {
|
||||
vtpc_move_resident_to_am(resident_idx);
|
||||
} else if (g_state.resident[resident_idx].list == VTPC_LIST_AM) {
|
||||
vtpc_resident_list_remove(&g_state.am, resident_idx);
|
||||
vtpc_resident_list_push_back(&g_state.am, resident_idx);
|
||||
}
|
||||
*out_idx = resident_idx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
++g_state.cache_miss;
|
||||
|
||||
if (vtpc_ensure_resident_capacity() < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ghost_idx = vtpc_find_ghost_page(file_slot, page_no);
|
||||
int idx = vtpc_alloc_resident_slot();
|
||||
if (idx < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
vtpc_resident_page* page = &g_state.resident[idx];
|
||||
page->file_slot = file_slot;
|
||||
page->page_no = page_no;
|
||||
page->dirty = 0;
|
||||
|
||||
if (need_existing) {
|
||||
vtpc_file* file = &g_state.files[file_slot];
|
||||
if (vtpc_pread_page(file->os_fd, page_no, page->data) < 0) {
|
||||
page->used = 0;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
memset(page->data, 0, g_state.page_size);
|
||||
}
|
||||
|
||||
if (ghost_idx >= 0) {
|
||||
vtpc_ghost_list_remove(&g_state.a1out, ghost_idx);
|
||||
g_state.ghosts[ghost_idx].used = 0;
|
||||
page->list = VTPC_LIST_AM;
|
||||
vtpc_resident_list_push_back(&g_state.am, idx);
|
||||
} else {
|
||||
page->list = VTPC_LIST_A1IN;
|
||||
vtpc_resident_list_push_back(&g_state.a1in, idx);
|
||||
}
|
||||
|
||||
*out_idx = idx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vtpc_flush_file_pages(vtpc_file* file, int file_slot, int with_fsync) {
|
||||
for (int i = 0; i < VTPC_MAX_CACHE_PAGES; ++i) {
|
||||
vtpc_resident_page* page = &g_state.resident[i];
|
||||
if (page->used && page->file_slot == file_slot && page->dirty) {
|
||||
if (vtpc_flush_page(i) < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (file->size_dirty) {
|
||||
if (ftruncate(file->os_fd, file->size) < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (with_fsync) {
|
||||
if (fsync(file->os_fd) < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
file->size_dirty = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void vtpc_drop_file_pages(int file_slot) {
|
||||
for (int i = 0; i < VTPC_MAX_CACHE_PAGES; ++i) {
|
||||
vtpc_resident_page* page = &g_state.resident[i];
|
||||
if (!page->used || page->file_slot != file_slot) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (page->list == VTPC_LIST_A1IN) {
|
||||
vtpc_resident_list_remove(&g_state.a1in, i);
|
||||
} else if (page->list == VTPC_LIST_AM) {
|
||||
vtpc_resident_list_remove(&g_state.am, i);
|
||||
}
|
||||
|
||||
page->used = 0;
|
||||
page->dirty = 0;
|
||||
page->list = VTPC_LIST_NONE;
|
||||
page->file_slot = -1;
|
||||
page->page_no = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < VTPC_MAX_A1OUT_PAGES; ++i) {
|
||||
vtpc_ghost_page* ghost = &g_state.ghosts[i];
|
||||
if (!ghost->used || ghost->file_slot != file_slot) {
|
||||
continue;
|
||||
}
|
||||
vtpc_ghost_list_remove(&g_state.a1out, i);
|
||||
ghost->used = 0;
|
||||
ghost->file_slot = -1;
|
||||
ghost->page_no = 0;
|
||||
}
|
||||
}
|
||||
107
lab/vtpc/lib/vtpc_internal.h
Normal file
107
lab/vtpc/lib/vtpc_internal.h
Normal file
@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#define VTPC_MAX_OPEN_FILES 256
|
||||
#define VTPC_MAX_CACHE_PAGES 1024
|
||||
#define VTPC_MAX_A1OUT_PAGES 1024
|
||||
|
||||
enum vtpc_resident_list {
|
||||
VTPC_LIST_NONE = 0,
|
||||
VTPC_LIST_A1IN = 1,
|
||||
VTPC_LIST_AM = 2,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int head;
|
||||
int tail;
|
||||
size_t size;
|
||||
} vtpc_list;
|
||||
|
||||
typedef struct {
|
||||
// Slot is occupied by a resident cached page.
|
||||
int used;
|
||||
int prev;
|
||||
int next;
|
||||
int list;
|
||||
// Page was modified in cache and must be flushed.
|
||||
int dirty;
|
||||
// Owner file index in g_state.files[].
|
||||
int file_slot;
|
||||
// File page number (offset / page_size).
|
||||
off_t page_no;
|
||||
unsigned char* data;
|
||||
} vtpc_resident_page;
|
||||
|
||||
typedef struct {
|
||||
int used;
|
||||
int prev;
|
||||
int next;
|
||||
int file_slot;
|
||||
off_t page_no;
|
||||
} vtpc_ghost_page;
|
||||
|
||||
typedef struct {
|
||||
int used;
|
||||
int os_fd;
|
||||
int open_flags;
|
||||
off_t pos;
|
||||
// Logical file size tracked by vtpc.
|
||||
off_t size;
|
||||
// 1 means ftruncate(size) is still pending.
|
||||
int size_dirty;
|
||||
} vtpc_file;
|
||||
|
||||
typedef struct {
|
||||
int initialized;
|
||||
size_t page_size;
|
||||
size_t max_resident;
|
||||
size_t max_a1out;
|
||||
// 2Q threshold: preferred upper bound for A1in size.
|
||||
size_t kin;
|
||||
|
||||
vtpc_list a1in;
|
||||
vtpc_list am;
|
||||
vtpc_list a1out;
|
||||
|
||||
uint64_t cache_hit;
|
||||
uint64_t cache_miss;
|
||||
uint64_t cache_all;
|
||||
|
||||
vtpc_file files[VTPC_MAX_OPEN_FILES];
|
||||
vtpc_resident_page resident[VTPC_MAX_CACHE_PAGES];
|
||||
vtpc_ghost_page ghosts[VTPC_MAX_A1OUT_PAGES];
|
||||
} vtpc_state;
|
||||
|
||||
extern vtpc_state g_state;
|
||||
|
||||
size_t vtpc_min_size(size_t lhs, size_t rhs);
|
||||
int vtpc_state_init(void);
|
||||
vtpc_file* vtpc_get_file(int fd);
|
||||
|
||||
void vtpc_resident_list_push_back(vtpc_list* list, int idx);
|
||||
void vtpc_resident_list_remove(vtpc_list* list, int idx);
|
||||
int vtpc_resident_list_pop_front(vtpc_list* list);
|
||||
|
||||
void vtpc_ghost_list_push_back(vtpc_list* list, int idx);
|
||||
void vtpc_ghost_list_remove(vtpc_list* list, int idx);
|
||||
int vtpc_ghost_list_pop_front(vtpc_list* list);
|
||||
|
||||
int vtpc_find_resident_page(int file_slot, off_t page_no);
|
||||
int vtpc_find_ghost_page(int file_slot, off_t page_no);
|
||||
int vtpc_alloc_resident_slot(void);
|
||||
int vtpc_alloc_ghost_slot(void);
|
||||
int vtpc_pread_page(int os_fd, off_t page_no, unsigned char* data);
|
||||
int vtpc_pwrite_page(int os_fd, off_t page_no, const unsigned char* data);
|
||||
int vtpc_flush_page(int idx);
|
||||
void vtpc_trim_a1out(void);
|
||||
int vtpc_evict_one(void);
|
||||
int vtpc_ensure_resident_capacity(void);
|
||||
void vtpc_move_resident_to_am(int idx);
|
||||
int vtpc_get_or_load_page(
|
||||
int file_slot, off_t page_no, int need_existing, int* out_idx
|
||||
);
|
||||
int vtpc_flush_file_pages(vtpc_file* file, int file_slot, int with_fsync);
|
||||
void vtpc_drop_file_pages(int file_slot);
|
||||
83
lab/vtpc/lib/vtpc_list.c
Normal file
83
lab/vtpc/lib/vtpc_list.c
Normal file
@ -0,0 +1,83 @@
|
||||
#include "vtpc_internal.h"
|
||||
|
||||
void vtpc_resident_list_push_back(vtpc_list* list, int idx) {
|
||||
vtpc_resident_page* page = &g_state.resident[idx];
|
||||
page->prev = list->tail;
|
||||
page->next = -1;
|
||||
if (list->tail != -1) {
|
||||
g_state.resident[list->tail].next = idx;
|
||||
} else {
|
||||
list->head = idx;
|
||||
}
|
||||
list->tail = idx;
|
||||
++list->size;
|
||||
}
|
||||
|
||||
void vtpc_resident_list_remove(vtpc_list* list, int idx) {
|
||||
vtpc_resident_page* page = &g_state.resident[idx];
|
||||
if (page->prev != -1) {
|
||||
g_state.resident[page->prev].next = page->next;
|
||||
} else {
|
||||
list->head = page->next;
|
||||
}
|
||||
if (page->next != -1) {
|
||||
g_state.resident[page->next].prev = page->prev;
|
||||
} else {
|
||||
list->tail = page->prev;
|
||||
}
|
||||
page->prev = -1;
|
||||
page->next = -1;
|
||||
if (list->size > 0) {
|
||||
--list->size;
|
||||
}
|
||||
}
|
||||
|
||||
int vtpc_resident_list_pop_front(vtpc_list* list) {
|
||||
if (list->head == -1) {
|
||||
return -1;
|
||||
}
|
||||
int idx = list->head;
|
||||
vtpc_resident_list_remove(list, idx);
|
||||
return idx;
|
||||
}
|
||||
|
||||
void vtpc_ghost_list_push_back(vtpc_list* list, int idx) {
|
||||
vtpc_ghost_page* page = &g_state.ghosts[idx];
|
||||
page->prev = list->tail;
|
||||
page->next = -1;
|
||||
if (list->tail != -1) {
|
||||
g_state.ghosts[list->tail].next = idx;
|
||||
} else {
|
||||
list->head = idx;
|
||||
}
|
||||
list->tail = idx;
|
||||
++list->size;
|
||||
}
|
||||
|
||||
void vtpc_ghost_list_remove(vtpc_list* list, int idx) {
|
||||
vtpc_ghost_page* page = &g_state.ghosts[idx];
|
||||
if (page->prev != -1) {
|
||||
g_state.ghosts[page->prev].next = page->next;
|
||||
} else {
|
||||
list->head = page->next;
|
||||
}
|
||||
if (page->next != -1) {
|
||||
g_state.ghosts[page->next].prev = page->prev;
|
||||
} else {
|
||||
list->tail = page->prev;
|
||||
}
|
||||
page->prev = -1;
|
||||
page->next = -1;
|
||||
if (list->size > 0) {
|
||||
--list->size;
|
||||
}
|
||||
}
|
||||
|
||||
int vtpc_ghost_list_pop_front(vtpc_list* list) {
|
||||
if (list->head == -1) {
|
||||
return -1;
|
||||
}
|
||||
int idx = list->head;
|
||||
vtpc_ghost_list_remove(list, idx);
|
||||
return idx;
|
||||
}
|
||||
64
lab/vtpc/lib/vtpc_state.c
Normal file
64
lab/vtpc/lib/vtpc_state.c
Normal file
@ -0,0 +1,64 @@
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "vtpc_internal.h"
|
||||
|
||||
vtpc_state g_state = {0};
|
||||
|
||||
static void vtpc_print_stats(void) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"vtpc stats: cache_hit=%llu cache_miss=%llu cache_all=%llu\n",
|
||||
(unsigned long long)g_state.cache_hit,
|
||||
(unsigned long long)g_state.cache_miss,
|
||||
(unsigned long long)g_state.cache_all
|
||||
);
|
||||
}
|
||||
|
||||
size_t vtpc_min_size(size_t lhs, size_t rhs) {
|
||||
return lhs < rhs ? lhs : rhs;
|
||||
}
|
||||
|
||||
int vtpc_state_init(void) {
|
||||
if (g_state.initialized) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long page_sz = sysconf(_SC_PAGESIZE);
|
||||
if (page_sz <= 0) {
|
||||
page_sz = 4096;
|
||||
}
|
||||
|
||||
g_state.page_size = (size_t)page_sz;
|
||||
g_state.max_resident = VTPC_MAX_CACHE_PAGES;
|
||||
g_state.max_a1out = VTPC_MAX_A1OUT_PAGES;
|
||||
g_state.kin = g_state.max_resident / 4;
|
||||
if (g_state.kin == 0) {
|
||||
g_state.kin = 1;
|
||||
}
|
||||
|
||||
g_state.a1in.head = -1;
|
||||
g_state.a1in.tail = -1;
|
||||
g_state.am.head = -1;
|
||||
g_state.am.tail = -1;
|
||||
g_state.a1out.head = -1;
|
||||
g_state.a1out.tail = -1;
|
||||
|
||||
if (atexit(vtpc_print_stats) != 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_state.initialized = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
vtpc_file* vtpc_get_file(int fd) {
|
||||
if (fd < 0 || fd >= VTPC_MAX_OPEN_FILES || !g_state.files[fd].used) {
|
||||
errno = EBADF;
|
||||
return NULL;
|
||||
}
|
||||
return &g_state.files[fd];
|
||||
}
|
||||
16
lab/vtpc/vtld/.clang-format
Normal file
16
lab/vtpc/vtld/.clang-format
Normal file
@ -0,0 +1,16 @@
|
||||
BasedOnStyle: Google
|
||||
|
||||
AccessModifierOffset: -2
|
||||
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
|
||||
DerivePointerAlignment: false
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
|
||||
AlignArrayOfStructures: Right
|
||||
5
lab/vtpc/vtld/.gitignore
vendored
Normal file
5
lab/vtpc/vtld/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
cpu_dedup
|
||||
cpu_dedup_pthreads
|
||||
ema_replace_str
|
||||
ema_replace_str_pthreads
|
||||
*.bin
|
||||
5
lab/vtpc/vtld/.vscode/settings.json
vendored
Normal file
5
lab/vtpc/vtld/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"errno.h": "c"
|
||||
}
|
||||
}
|
||||
165
lab/vtpc/vtld/README.md
Normal file
165
lab/vtpc/vtld/README.md
Normal file
@ -0,0 +1,165 @@
|
||||
# Базовый трек. Лабораторная работа 1
|
||||
|
||||
Вариант: `cpu-dedup ema-replace-str`
|
||||
|
||||
## Часть 3. Анализ системы и мониторинг
|
||||
|
||||
Разработайте параметризируемую программу-нагрузчик, которая будет однопоточно нагружать подсистему ввода-вывода (IO). Она должна принимать на вход следующие параметры:
|
||||
|
||||
- `rw: read/write` - режим нагрузки: чтение или запись;
|
||||
- `block_size: <number>` - размер блока в байтах, с которым производится чтение/запись;
|
||||
- `block_count: <number>` - количество блоков;
|
||||
- `file: <path string>` - имя файла, с которым происходит работа;
|
||||
- `range: <number>-<number>` - границы в пределах файла, в которые должны
|
||||
осуществляться запись/чтение, значение по умолчанию, 0-0, означает, что доступен
|
||||
весь файл;
|
||||
- `direct: on/off` - открывать файл с опцией O_DIRECT (в обход кэшей ОС) или нет;
|
||||
- `type: sequence/random` - режим выбора следующего блока для записи/чтения
|
||||
последовательно или случайно;
|
||||
|
||||
Разработайте комплекс программ-нагрузчиков согласно выданному варианту.
|
||||
Каждый нагрузчик должен принимать параметр, который определяет
|
||||
количество повторений для алгоритма, указанного в задании, а также другие
|
||||
вспомогательные параметры. Варианты программ рассчитаны так, чтобы по-разному
|
||||
нагружать вычислительную подсистему (CPU) и подсистему ввода-вывода (IO) - это
|
||||
необходимо учитывать при их реализации. Разработанные программы необходимо
|
||||
скомпилировать без дополнительных опций оптимизации компилятора.
|
||||
|
||||
Проведите исследование поведения ОС во время исполнения разработанных
|
||||
программ-нагрузчиков по следующему плану:
|
||||
|
||||
1. Перед запуском нагрузчика, попробуйте оценить время работы вашей программы
|
||||
или ее результаты (если по варианту вам досталось измерение чего либо) и
|
||||
обоснуйте свои предположения.
|
||||
|
||||
2. Запустите программу-нагрузчик и зафиксируйте метрики ее работы с помощью
|
||||
инструментов для мониторинга и профилирования (см. лекции). Сравните полученные
|
||||
результаты с ожидаемыми. Объяснените наблюдаемое поведение. Продолжительность
|
||||
каждого запуска должна занимать достаточное для прекращения переходных
|
||||
процессов время, по крайней мере, минуту.
|
||||
|
||||
3. Определите количество одновременно запущенных процессов с программой-нагрузчиком,
|
||||
которое эффективно нагружает все ядра процессора в вашей системе. Как
|
||||
распределяются показатели времени USER%, SYS%, WAIT%, а также полное время
|
||||
выполнения нагрузчика, какое количество переключений контекста (вынужденных и
|
||||
невынужденных) происходит при этом? Подумайте над тем, как вы определяете
|
||||
эффективность.
|
||||
|
||||
4. Увеличьте количество нагрузчиков вдвое, втрое, вчетверо. Как изменились
|
||||
исследуемые показатели? Почему?
|
||||
|
||||
5. Объедините программы-нагрузчики в одну, реализованную при помощи потоков
|
||||
выполнения, чтобы один нагрузчик эффективно нагружал все ядра вашей системы. Как
|
||||
изменились показатели времени для того же объема вычислений? Запустите одну,
|
||||
две, три таких программы. Как изменились исследуемые показатели? Почему?
|
||||
|
||||
6. Скомпилируйте программу-нугрузчик с опцией агрессивной оптимизации. Как
|
||||
изменились исследуемые показатели? На сколько сократилось реальное время
|
||||
исполнения программы нагрузчика? Почему?
|
||||
|
||||
В процессе защиты вашей работы преподаватель будет просить вас запустить
|
||||
программу-нагрузчик с различными комбинациями параметров и просить объяснить
|
||||
результат.
|
||||
|
||||
## Требования к реализации
|
||||
|
||||
1. Программа (комплекс программ) должна быть реализован на языке C.
|
||||
|
||||
2. Дочерние процессы должны быть созданы через указанные в варианте системные
|
||||
вызовы операционной системы, с обеспечением корректного запуска и завершения
|
||||
процессов. Запрещено использовать высокоуровневые абстракции над системными
|
||||
вызовами. Необходимо использовать, процедуры libc.
|
||||
|
||||
## Требования к отчету и защите
|
||||
|
||||
Отчет должен содержать:
|
||||
|
||||
1. Титульный лист с указанием номера и названия ЛР, вашего ФИО, ФИО
|
||||
преподавателя практики, номера вашей группы, варианта ЛР.
|
||||
|
||||
2. Текст задания в соответствии с вариантом.
|
||||
|
||||
3. Листинг исходного кода всех программ, написанных в рамках данной ЛР.
|
||||
|
||||
4. Предположения о свойствах программ-нагрузчиков
|
||||
|
||||
5. Результаты измерений и метрик программ-нагрузчиков, полученных инструментами
|
||||
мониторинга. Должно быть описано, какие утилиты запускались, с какими
|
||||
параметрами и их вывод.
|
||||
|
||||
6. Сравнительный анализ ожидаемых и фактических значений исследованных показателей.
|
||||
|
||||
7. Заключение.
|
||||
|
||||
Студент должен быть готов:
|
||||
|
||||
1. Продемонстрировать работоспособность Shell и предоставить исходный код.
|
||||
|
||||
2. Воспроизвести ход работы по исследованию поведения ОС и продемонстрировать схожие
|
||||
результаты работы программ-нагрузчиков.
|
||||
|
||||
3. Ответить на вопросы по реализации программ и контрольные вопросы.
|
||||
|
||||
## Описание вариантов
|
||||
|
||||
Нагрузчик вычислительной подсистемы (группа **cpu**):
|
||||
|
||||
- `cpu-factorize`: Разложение числа на простые множители. Задаваемое число
|
||||
должно являться произведением нескольких больших простых чисел.
|
||||
|
||||
- `cpu-sort`: Любая сортировка массива чисел за N*log(N) и любая за N**2.
|
||||
Алгоритм должен быть реализован самим студентом, а не взят из стандартной
|
||||
библиотеки.
|
||||
|
||||
- `cpu-short-path`: Поиск кратчайшего пути в графе. Граф генерируется в момент
|
||||
запуска или записан в исходный код.
|
||||
|
||||
- `cpu-linreg`: Построение модели линейной регрессии для последовательности случайных чисел. Количество чисел и диапозон значений задаются параметром.
|
||||
|
||||
- `cpu-dedup`: Дедупликация элементов в массиве. Массив генерируется на основе
|
||||
случайных чисел. Размер массива задаётся параметром.
|
||||
|
||||
- `cpu-mat-mul`: Перемножение матриц. Размер матриц задаётся параметром, а
|
||||
элементы матриц генерируются на основе случайных чисел.
|
||||
|
||||
- `cpu-calc-md5`: Подсчитать хеш md5 для текста полученного конкатенацией
|
||||
фрагментов текста выбранных с помощью генератора случайных чисел.
|
||||
|
||||
- `cpu-calc-crc`: Посчитать контрольную сумму crc для текста полученного
|
||||
конкатенацией фрагментов текста выбранных с помощью генератора случайных чисел.
|
||||
|
||||
|
||||
Нагрузчик, работающий с внешней памятью (группа **ema**):
|
||||
|
||||
- `ema-replace-int`: Поиск элемента в массиве чисел во внешней памяти и замена
|
||||
значения на другое. Файл для поиска значения генерируется в произвольном
|
||||
формате, размер задаётся через параметр.
|
||||
|
||||
- `ema-replace-str`: Поиск подстроки в тексте во внешней памяти и замена на
|
||||
другую строку того же размера. Файл для поиска значения генерируется в
|
||||
произвольном формате, размер задаётся через параметр.
|
||||
|
||||
- `ema-sort-int`: Сортировка массива чисел во внешней памяти. Весь массив не
|
||||
может быть загружен в память, поэтому сортировка происходит частями.
|
||||
|
||||
- `ema-traverse-graph`: Обход k-регулярного направленного графа,
|
||||
сериализованного в файл, и выполнение модифицирующей операции над определенной
|
||||
вершиной графа. Поиск вершины происходит по хранимому в ней значению, возможно с
|
||||
ограничением глубины поиска.
|
||||
Формат хранения: вершина (struct Node) определяется её отступом от начала файла (id=index*(sizeof struct Node)), в вершине хранится одно значение типа фиксированного размера и k номеров вершин, в которые из неё можно попасть; в файле вершины хранятся последовательно с выравниванием соответствующим размеру структуры. Тип значения, хранимого в вершине, и значение k подбираются таким образом, чтобы с учетом выравнивания не было неиспользуемой памяти.
|
||||
При генерации графа случайно задается значение, хранимое в вершине и номера соседних вершин, отдельным параметром генератора задется шанс получения номера соседней вершины больше или меньше номера данной вершины (соседняя вершина раньше или позже).
|
||||
|
||||
- `ema-join-*`: Необходимо реализовать алгоритм equi-join для объединения
|
||||
таблиц в файлах. На вход подаются два файла, результат их объединения пишется в
|
||||
новый файл. В первой строке файла содержится количество строчек в файле. В
|
||||
каждой последующей строчке входного файла содержится два значения, разделенных
|
||||
пробелами: численный идентификатор и строка без пробелов фиксированного размера
|
||||
8 байт. Строка является валидным английским словом. Таблицы необходимо
|
||||
объединять по идентификатору.
|
||||
|
||||
По варианту выдается стратегия джоина:
|
||||
- `ema-join-nl`: Nested Loop Join
|
||||
- `ema-join-hash`: Hash Join Join
|
||||
- `ema-join-sm`: Sort + Merge Join
|
||||
|
||||
Количество строк входных таблиц включают все комбинации пар значений: 5, 10, 100, 1000, 10000."
|
||||
201
lab/vtpc/vtld/ema_replace_str.c
Normal file
201
lab/vtpc/vtld/ema_replace_str.c
Normal file
@ -0,0 +1,201 @@
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "ema_utils.h"
|
||||
#include "util.h"
|
||||
#include "vtpc.h"
|
||||
|
||||
static void print_usage(const char* prog) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Usage: %s --block_size N --file PATH --repeats N --string STR "
|
||||
"--replace STR [--size N] [--range A-B] "
|
||||
"[--type sequence/random]\n",
|
||||
prog
|
||||
);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
const char* block_size_s = get_opt_value(argc, argv, "block_size");
|
||||
const char* file_s = get_opt_value(argc, argv, "file");
|
||||
const char* range_s = get_opt_value(argc, argv, "range");
|
||||
const char* type_s = get_opt_value(argc, argv, "type");
|
||||
const char* repeats_s = get_opt_value(argc, argv, "repeats");
|
||||
const char* string_s = get_opt_value(argc, argv, "string");
|
||||
const char* replace_s = get_opt_value(argc, argv, "replace");
|
||||
const char* size_s = get_opt_value(argc, argv, "size");
|
||||
|
||||
if (!block_size_s || !file_s || !repeats_s || !string_s || !replace_s) {
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint64_t block_size_u = 0;
|
||||
uint64_t repeats = 0;
|
||||
uint64_t gen_size = 0;
|
||||
if (!parse_u64(block_size_s, &block_size_u) ||
|
||||
!parse_u64(repeats_s, &repeats)) {
|
||||
fprintf(stderr, "Invalid block_size or repeats.\n");
|
||||
return 1;
|
||||
}
|
||||
if (size_s && !parse_u64(size_s, &gen_size)) {
|
||||
fprintf(stderr, "Invalid size.\n");
|
||||
return 1;
|
||||
}
|
||||
if (block_size_u == 0 || repeats == 0) {
|
||||
fprintf(stderr, "block_size and repeats must be > 0.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t block_size = (size_t)block_size_u;
|
||||
size_t pattern_len = strlen(string_s);
|
||||
if (pattern_len == 0 || strlen(replace_s) != pattern_len) {
|
||||
fprintf(stderr, "string and replace must be non-empty and same length.\n");
|
||||
return 1;
|
||||
}
|
||||
if (pattern_len > block_size) {
|
||||
fprintf(stderr, "string length must be <= block_size.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int random_mode = 0;
|
||||
if (type_s) {
|
||||
if (strcmp(type_s, "random") == 0) {
|
||||
random_mode = 1;
|
||||
} else if (strcmp(type_s, "sequence") != 0) {
|
||||
fprintf(stderr, "Invalid type value: use sequence/random.\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t range_start = 0;
|
||||
uint64_t range_end = 0;
|
||||
if (!parse_range(range_s, &range_start, &range_end)) {
|
||||
fprintf(stderr, "Invalid range format, expected A-B.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
srand((unsigned)time(NULL));
|
||||
|
||||
if (size_s) {
|
||||
if (gen_size == 0) {
|
||||
fprintf(stderr, "size must be > 0.\n");
|
||||
return 1;
|
||||
}
|
||||
if (!generate_file(file_s, gen_size, block_size, string_s)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int fd = vtpc_open(file_s, O_RDWR, 0);
|
||||
if (fd < 0) {
|
||||
perror("vtpc_open");
|
||||
return 1;
|
||||
}
|
||||
|
||||
off_t file_size_off = vtpc_lseek(fd, 0, SEEK_END);
|
||||
if (file_size_off < 0) {
|
||||
perror("vtpc_lseek");
|
||||
vtpc_close(fd);
|
||||
return 1;
|
||||
}
|
||||
uint64_t file_size = (uint64_t)file_size_off;
|
||||
if (file_size == 0) {
|
||||
fprintf(stderr, "File is empty.\n");
|
||||
vtpc_close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (range_start == 0 && range_end == 0) {
|
||||
range_start = 0;
|
||||
range_end = file_size;
|
||||
}
|
||||
if (range_end > file_size || range_start >= range_end) {
|
||||
fprintf(stderr, "Invalid range bounds.\n");
|
||||
vtpc_close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint64_t range_len = range_end - range_start;
|
||||
uint64_t full_blocks = range_len / block_size;
|
||||
uint64_t tail = range_len % block_size;
|
||||
uint64_t blocks = full_blocks + ((tail > 0) ? 1 : 0);
|
||||
if (blocks == 0) {
|
||||
fprintf(stderr, "No blocks to process.\n");
|
||||
vtpc_close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void* buf = malloc(block_size);
|
||||
if (!buf) {
|
||||
fprintf(stderr, "malloc failed.\n");
|
||||
vtpc_close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint64_t total_replaced = 0;
|
||||
for (uint64_t i = 0; i < repeats; ++i) {
|
||||
uint64_t block_index =
|
||||
random_mode ? (uint64_t)(rand() % (int)blocks) : (i % blocks);
|
||||
uint64_t offset = range_start + block_index * block_size;
|
||||
size_t len = block_size;
|
||||
if (tail > 0 && block_index == blocks - 1) {
|
||||
len = (size_t)tail;
|
||||
}
|
||||
|
||||
if (vtpc_lseek(fd, (off_t)offset, SEEK_SET) == (off_t)-1) {
|
||||
perror("vtpc_lseek");
|
||||
free(buf);
|
||||
vtpc_close(fd);
|
||||
return 1;
|
||||
}
|
||||
ssize_t r = vtpc_read(fd, buf, len);
|
||||
if (r < 0) {
|
||||
perror("vtpc_read");
|
||||
free(buf);
|
||||
vtpc_close(fd);
|
||||
return 1;
|
||||
}
|
||||
if (r == 0) {
|
||||
continue;
|
||||
}
|
||||
size_t replaced =
|
||||
replace_in_buffer((char*)buf, (size_t)r, string_s, replace_s);
|
||||
if (replaced > 0) {
|
||||
if (vtpc_lseek(fd, (off_t)offset, SEEK_SET) == (off_t)-1) {
|
||||
perror("vtpc_lseek");
|
||||
free(buf);
|
||||
vtpc_close(fd);
|
||||
return 1;
|
||||
}
|
||||
ssize_t w = vtpc_write(fd, buf, (size_t)r);
|
||||
if (w < 0 || w != r) {
|
||||
perror("vtpc_write");
|
||||
free(buf);
|
||||
vtpc_close(fd);
|
||||
return 1;
|
||||
}
|
||||
total_replaced += replaced;
|
||||
}
|
||||
}
|
||||
|
||||
printf("replacements=%" PRIu64 "\n", total_replaced);
|
||||
|
||||
if (vtpc_fsync(fd) != 0) {
|
||||
perror("vtpc_fsync");
|
||||
free(buf);
|
||||
vtpc_close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
free(buf);
|
||||
vtpc_close(fd);
|
||||
return 0;
|
||||
}
|
||||
126
lab/vtpc/vtld/ema_utils.c
Normal file
126
lab/vtpc/vtld/ema_utils.c
Normal file
@ -0,0 +1,126 @@
|
||||
#include "ema_utils.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int parse_u64(const char* s, uint64_t* out) {
|
||||
if (!s || !*s) {
|
||||
return 0;
|
||||
}
|
||||
char* end = NULL;
|
||||
errno = 0;
|
||||
unsigned long long v = strtoull(s, &end, 10);
|
||||
if (errno != 0 || !end || *end != '\0') {
|
||||
return 0;
|
||||
}
|
||||
*out = (uint64_t)v;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int parse_range(const char* s, uint64_t* start, uint64_t* end) {
|
||||
if (!s) {
|
||||
*start = 0;
|
||||
*end = 0;
|
||||
return 1;
|
||||
}
|
||||
const char* dash = strchr(s, '-');
|
||||
if (!dash) {
|
||||
return 0;
|
||||
}
|
||||
char left[32];
|
||||
char right[32];
|
||||
size_t left_len = (size_t)(dash - s);
|
||||
size_t right_len = strlen(dash + 1);
|
||||
if (left_len == 0 || right_len == 0 || left_len >= sizeof(left) ||
|
||||
right_len >= sizeof(right)) {
|
||||
return 0;
|
||||
}
|
||||
memcpy(left, s, left_len);
|
||||
left[left_len] = '\0';
|
||||
memcpy(right, dash + 1, right_len);
|
||||
right[right_len] = '\0';
|
||||
if (!parse_u64(left, start) || !parse_u64(right, end)) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int open_file_direct(const char* path, int direct) {
|
||||
int flags = O_RDWR;
|
||||
#ifdef O_DIRECT
|
||||
if (direct) {
|
||||
flags |= O_DIRECT;
|
||||
}
|
||||
#else
|
||||
if (direct) {
|
||||
fprintf(stderr, "Warning: O_DIRECT is not supported, ignoring.\n");
|
||||
}
|
||||
#endif
|
||||
return open(path, flags);
|
||||
}
|
||||
|
||||
int generate_file(
|
||||
const char* path, uint64_t size, size_t block_size, const char* pattern
|
||||
) {
|
||||
int fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644);
|
||||
if (fd < 0) {
|
||||
perror("open");
|
||||
return 0;
|
||||
}
|
||||
size_t pat_len = strlen(pattern);
|
||||
char* buf = malloc(block_size);
|
||||
if (!buf) {
|
||||
fprintf(stderr, "malloc failed.\n");
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
uint64_t remaining = size;
|
||||
while (remaining > 0) {
|
||||
size_t chunk = (remaining > block_size) ? block_size : (size_t)remaining;
|
||||
for (size_t i = 0; i < chunk; ++i) {
|
||||
buf[i] = (char)('a' + (rand() % 26));
|
||||
}
|
||||
if (pat_len > 0 && pat_len <= chunk && (rand() % 3) == 0) {
|
||||
size_t pos = (size_t)(rand() % (chunk - pat_len + 1));
|
||||
memcpy(buf + pos, pattern, pat_len);
|
||||
}
|
||||
ssize_t w = write(fd, buf, chunk);
|
||||
if (w < 0 || (size_t)w != chunk) {
|
||||
perror("write");
|
||||
free(buf);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
remaining -= chunk;
|
||||
}
|
||||
free(buf);
|
||||
if (fsync(fd) != 0) {
|
||||
perror("fsync");
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t replace_in_buffer(
|
||||
char* buf, size_t len, const char* needle, const char* repl
|
||||
) {
|
||||
size_t nlen = strlen(needle);
|
||||
if (nlen == 0 || nlen > len) {
|
||||
return 0;
|
||||
}
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i + nlen <= len; ++i) {
|
||||
if (memcmp(buf + i, needle, nlen) == 0) {
|
||||
memcpy(buf + i, repl, nlen);
|
||||
++count;
|
||||
i += nlen - 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
14
lab/vtpc/vtld/ema_utils.h
Normal file
14
lab/vtpc/vtld/ema_utils.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
int parse_u64(const char* s, uint64_t* out);
|
||||
int parse_range(const char* s, uint64_t* start, uint64_t* end);
|
||||
int open_file_direct(const char* path, int direct);
|
||||
int generate_file(
|
||||
const char* path, uint64_t size, size_t block_size, const char* pattern
|
||||
);
|
||||
size_t replace_in_buffer(
|
||||
char* buf, size_t len, const char* needle, const char* repl
|
||||
);
|
||||
10
lab/vtpc/vtld/makefile
Normal file
10
lab/vtpc/vtld/makefile
Normal file
@ -0,0 +1,10 @@
|
||||
CC=gcc
|
||||
CFLAGS=-O3 -D_GNU_SOURCE -I../lib
|
||||
|
||||
all: ema_replace_str
|
||||
|
||||
ema_replace_str: ema_replace_str.c ema_utils.c
|
||||
$(CC) $(CFLAGS) -o ema_replace_str ema_replace_str.c ema_utils.c util.c ../lib/vtpc.c ../lib/vtpc_state.c ../lib/vtpc_list.c ../lib/vtpc_cache.c
|
||||
|
||||
clean:
|
||||
rm -f ema_replace_str
|
||||
21
lab/vtpc/vtld/util.c
Normal file
21
lab/vtpc/vtld/util.c
Normal file
@ -0,0 +1,21 @@
|
||||
#include "util.h"
|
||||
|
||||
const char* get_opt_value(int argc, char** argv, const char* key) {
|
||||
size_t key_len = strlen(key);
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const char* arg = argv[i];
|
||||
while (*arg == '-') {
|
||||
++arg;
|
||||
}
|
||||
const char* eq = strchr(arg, '=');
|
||||
if (eq) {
|
||||
size_t len = (size_t)(eq - arg);
|
||||
if (len == key_len && strncmp(arg, key, len) == 0) {
|
||||
return eq + 1;
|
||||
}
|
||||
} else if (strcmp(arg, key) == 0 && i + 1 < argc) {
|
||||
return argv[i + 1];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
6
lab/vtpc/vtld/util.h
Normal file
6
lab/vtpc/vtld/util.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
const char* get_opt_value(int argc, char** argv, const char* key);
|
||||
Loading…
Reference in New Issue
Block a user