Added some comments to clarify how Coffee works internally

This commit is contained in:
nvt-se 2010-01-19 16:18:34 +00:00
parent 9d751da8e5
commit 3c890c48a9

View file

@ -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)) {