Added some comments to clarify how Coffee works internally
This commit is contained in:
parent
9d751da8e5
commit
3c890c48a9
|
@ -79,6 +79,7 @@
|
||||||
/* "Reluctant" garbage collection stops after erasing one sector. */
|
/* "Reluctant" garbage collection stops after erasing one sector. */
|
||||||
#define GC_RELUCTANT 1
|
#define GC_RELUCTANT 1
|
||||||
|
|
||||||
|
/* File descriptor macros. */
|
||||||
#define FD_VALID(fd) \
|
#define FD_VALID(fd) \
|
||||||
((fd) >= 0 && (fd) < COFFEE_FD_SET_SIZE && \
|
((fd) >= 0 && (fd) < COFFEE_FD_SET_SIZE && \
|
||||||
coffee_fd_set[(fd)].flags != COFFEE_FD_FREE)
|
coffee_fd_set[(fd)].flags != COFFEE_FD_FREE)
|
||||||
|
@ -86,6 +87,7 @@
|
||||||
#define FD_WRITABLE(fd) (coffee_fd_set[(fd)].flags & CFS_WRITE)
|
#define FD_WRITABLE(fd) (coffee_fd_set[(fd)].flags & CFS_WRITE)
|
||||||
#define FD_APPENDABLE(fd) (coffee_fd_set[(fd)].flags & CFS_APPEND)
|
#define FD_APPENDABLE(fd) (coffee_fd_set[(fd)].flags & CFS_APPEND)
|
||||||
|
|
||||||
|
/* File object macros. */
|
||||||
#define FILE_MODIFIED(file) ((file)->flags & COFFEE_FILE_MODIFIED)
|
#define FILE_MODIFIED(file) ((file)->flags & COFFEE_FILE_MODIFIED)
|
||||||
#define FILE_FREE(file) ((file)->max_pages == 0)
|
#define FILE_FREE(file) ((file)->max_pages == 0)
|
||||||
#define FILE_UNREFERENCED(file) ((file)->references == 0)
|
#define FILE_UNREFERENCED(file) ((file)->references == 0)
|
||||||
|
@ -98,6 +100,7 @@
|
||||||
#define HDR_FLAG_LOG 0x10 /* Log file. */
|
#define HDR_FLAG_LOG 0x10 /* Log file. */
|
||||||
#define HDR_FLAG_ISOLATED 0x20 /* Isolated page. */
|
#define HDR_FLAG_ISOLATED 0x20 /* Isolated page. */
|
||||||
|
|
||||||
|
/* File header macros. */
|
||||||
#define CHECK_FLAG(hdr, flag) ((hdr).flags & (flag))
|
#define CHECK_FLAG(hdr, flag) ((hdr).flags & (flag))
|
||||||
#define HDR_VALID(hdr) CHECK_FLAG(hdr, HDR_FLAG_VALID)
|
#define HDR_VALID(hdr) CHECK_FLAG(hdr, HDR_FLAG_VALID)
|
||||||
#define HDR_ALLOCATED(hdr) CHECK_FLAG(hdr, HDR_FLAG_ALLOCATED)
|
#define HDR_ALLOCATED(hdr) CHECK_FLAG(hdr, HDR_FLAG_ALLOCATED)
|
||||||
|
@ -110,18 +113,21 @@
|
||||||
!HDR_OBSOLETE(hdr) && \
|
!HDR_OBSOLETE(hdr) && \
|
||||||
!HDR_ISOLATED(hdr))
|
!HDR_ISOLATED(hdr))
|
||||||
|
|
||||||
|
/* Shortcuts derived from the hardware-dependent configuration of Coffee. */
|
||||||
#define COFFEE_SECTOR_COUNT (unsigned)(COFFEE_SIZE / COFFEE_SECTOR_SIZE)
|
#define COFFEE_SECTOR_COUNT (unsigned)(COFFEE_SIZE / COFFEE_SECTOR_SIZE)
|
||||||
#define COFFEE_PAGE_COUNT \
|
#define COFFEE_PAGE_COUNT \
|
||||||
((coffee_page_t)(COFFEE_SIZE / COFFEE_PAGE_SIZE))
|
((coffee_page_t)(COFFEE_SIZE / COFFEE_PAGE_SIZE))
|
||||||
#define COFFEE_PAGES_PER_SECTOR \
|
#define COFFEE_PAGES_PER_SECTOR \
|
||||||
((coffee_page_t)(COFFEE_SECTOR_SIZE / COFFEE_PAGE_SIZE))
|
((coffee_page_t)(COFFEE_SECTOR_SIZE / COFFEE_PAGE_SIZE))
|
||||||
|
|
||||||
|
/* This structure is used for garbage collection statistics. */
|
||||||
struct sector_status {
|
struct sector_status {
|
||||||
coffee_page_t active;
|
coffee_page_t active;
|
||||||
coffee_page_t obsolete;
|
coffee_page_t obsolete;
|
||||||
coffee_page_t free;
|
coffee_page_t free;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* The structure of cached file objects. */
|
||||||
struct file {
|
struct file {
|
||||||
cfs_offset_t end;
|
cfs_offset_t end;
|
||||||
coffee_page_t page;
|
coffee_page_t page;
|
||||||
|
@ -131,12 +137,15 @@ struct file {
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* The file descriptor structure. */
|
||||||
struct file_desc {
|
struct file_desc {
|
||||||
cfs_offset_t offset;
|
cfs_offset_t offset;
|
||||||
struct file *file;
|
struct file *file;
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* The file header structure mimics the representation of file headers
|
||||||
|
in the physical storage medium. */
|
||||||
struct file_header {
|
struct file_header {
|
||||||
coffee_page_t log_page;
|
coffee_page_t log_page;
|
||||||
uint16_t log_records;
|
uint16_t log_records;
|
||||||
|
@ -154,6 +163,12 @@ struct log_param {
|
||||||
uint16_t size;
|
uint16_t size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The protected memory consists of structures that should not be
|
||||||
|
* overwritten during system checkpointing because they may be used by
|
||||||
|
* the checkpointing implementation. These structures need not be
|
||||||
|
* protected if checkpointing is not used.
|
||||||
|
*/
|
||||||
static struct protected_mem_t {
|
static struct protected_mem_t {
|
||||||
struct file coffee_files[COFFEE_MAX_OPEN_FILES];
|
struct file coffee_files[COFFEE_MAX_OPEN_FILES];
|
||||||
struct file_desc coffee_fd_set[COFFEE_FD_SET_SIZE];
|
struct file_desc coffee_fd_set[COFFEE_FD_SET_SIZE];
|
||||||
|
@ -203,6 +218,11 @@ get_sector_status(uint16_t sector, struct sector_status *stats)
|
||||||
memset(stats, 0, sizeof(*stats));
|
memset(stats, 0, sizeof(*stats));
|
||||||
active = obsolete = free = 0;
|
active = obsolete = free = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_sector_status() is an iterative function using local static
|
||||||
|
* state. It therefore requires the the caller loops starts from
|
||||||
|
* sector 0 in order to reset the internal state.
|
||||||
|
*/
|
||||||
if(sector == 0) {
|
if(sector == 0) {
|
||||||
skip_pages = 0;
|
skip_pages = 0;
|
||||||
last_pages_are_active = 0;
|
last_pages_are_active = 0;
|
||||||
|
@ -211,6 +231,11 @@ get_sector_status(uint16_t sector, struct sector_status *stats)
|
||||||
sector_start = sector * COFFEE_PAGES_PER_SECTOR;
|
sector_start = sector * COFFEE_PAGES_PER_SECTOR;
|
||||||
sector_end = sector_start + COFFEE_PAGES_PER_SECTOR;
|
sector_end = sector_start + COFFEE_PAGES_PER_SECTOR;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Account for pages belonging to a file starting in a previous
|
||||||
|
* segment that extends into this segment. If the whole segment is
|
||||||
|
* covered, we do not need to continue counting pages in this iteration.
|
||||||
|
*/
|
||||||
if(last_pages_are_active) {
|
if(last_pages_are_active) {
|
||||||
if(skip_pages >= COFFEE_PAGES_PER_SECTOR) {
|
if(skip_pages >= COFFEE_PAGES_PER_SECTOR) {
|
||||||
stats->active = COFFEE_PAGES_PER_SECTOR;
|
stats->active = COFFEE_PAGES_PER_SECTOR;
|
||||||
|
@ -227,6 +252,8 @@ get_sector_status(uint16_t sector, struct sector_status *stats)
|
||||||
obsolete = skip_pages;
|
obsolete = skip_pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Determine the amount of pages of each type that have not been
|
||||||
|
accounted for yet in the current sector. */
|
||||||
for(page = sector_start + skip_pages; page < sector_end;) {
|
for(page = sector_start + skip_pages; page < sector_end;) {
|
||||||
read_header(&hdr, page);
|
read_header(&hdr, page);
|
||||||
last_pages_are_active = 0;
|
last_pages_are_active = 0;
|
||||||
|
@ -246,6 +273,14 @@ get_sector_status(uint16_t sector, struct sector_status *stats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determine the amount of pages in the following sectors that
|
||||||
|
* should be remembered for the next iteration. This is necessary
|
||||||
|
* because no page except the first of a file contains information
|
||||||
|
* about what type of page it is. A side effect of remembering this
|
||||||
|
* amount is that there is no need to read in the headers of each
|
||||||
|
* of these pages from the storage.
|
||||||
|
*/
|
||||||
skip_pages = active + obsolete + free - COFFEE_PAGES_PER_SECTOR;
|
skip_pages = active + obsolete + free - COFFEE_PAGES_PER_SECTOR;
|
||||||
if(skip_pages > 0) {
|
if(skip_pages > 0) {
|
||||||
if(last_pages_are_active) {
|
if(last_pages_are_active) {
|
||||||
|
@ -259,6 +294,13 @@ get_sector_status(uint16_t sector, struct sector_status *stats)
|
||||||
stats->obsolete = obsolete;
|
stats->obsolete = obsolete;
|
||||||
stats->free = free;
|
stats->free = free;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* To avoid unnecessary page isolation, we notify the callee that
|
||||||
|
* "skip_pages" pages should be isolated only the current file extent
|
||||||
|
* ends in the next sector. If the file extent ends in a more distant
|
||||||
|
* sector, however, the garbage collection can free the next sector
|
||||||
|
* immediately without requiring page isolation.
|
||||||
|
*/
|
||||||
return (last_pages_are_active || (skip_pages >= COFFEE_PAGES_PER_SECTOR)) ?
|
return (last_pages_are_active || (skip_pages >= COFFEE_PAGES_PER_SECTOR)) ?
|
||||||
0 : skip_pages;
|
0 : skip_pages;
|
||||||
}
|
}
|
||||||
|
@ -334,6 +376,16 @@ collect_garbage(int mode)
|
||||||
static coffee_page_t
|
static coffee_page_t
|
||||||
next_file(coffee_page_t page, struct file_header *hdr)
|
next_file(coffee_page_t page, struct file_header *hdr)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* The quick-skip algorithm for finding file extents is the most
|
||||||
|
* essential part of Coffee. The file allocation rules enables this
|
||||||
|
* algorithm to quickly jump over free areas and allocated extents
|
||||||
|
* after reading single headers and determining their status.
|
||||||
|
*
|
||||||
|
* The worst-case performance occurs when we encounter multiple long
|
||||||
|
* sequences of isolated pages, but such sequences are uncommon and
|
||||||
|
* always shorter than a sector.
|
||||||
|
*/
|
||||||
if(HDR_FREE(*hdr)) {
|
if(HDR_FREE(*hdr)) {
|
||||||
return (page + COFFEE_PAGES_PER_SECTOR) & ~(COFFEE_PAGES_PER_SECTOR - 1);
|
return (page + COFFEE_PAGES_PER_SECTOR) & ~(COFFEE_PAGES_PER_SECTOR - 1);
|
||||||
} else if(HDR_ISOLATED(*hdr)) {
|
} else if(HDR_ISOLATED(*hdr)) {
|
||||||
|
|
Loading…
Reference in a new issue