int fd1 = open(/file1, “O_RDWR”) ;
int fd2 = open(/file2, “O_RDWR”) ;
struct TxInfo { int num; int *fdSet;} ;
struct TxInfo info ;
info.num = 2 ;
info.fdSet = (int *) malloc(2 * sizeof(int)) ;
info.fdSet[0] = fd1, info.fdSet[1] = fd2 ;
// transaction begin
unsigned long TxID = tx_begin(&info) ;
write(fd1, “data1”) ;
write(fd2, “data2”) ;
tx_commit(TxID) ; // commit the transaction
// transaction end
free(info.fdSet) ;
Fig. 1. Sample Code to use FCFS. Code snippet that implements failure-
consistent updates of two existing files with FCFS’s transactional interfaces.
(3) tx_commit(TxID) commits a transaction; and (4)
tx_abort(TxID) cancels a transaction entirely.
In FCFS, to process a set of file system operations atom-
ically, applications only need to start a new transaction
using the tx_begin() call before these operations and
end the transaction with tx_commit() (or cancel it with
tx_abort()) after them, rather than implementing complex
consistent update protocols. To let the file system know
which operations belong to a specified transaction, application
developers should relate the corresponding file descriptors to
the transaction, passing these information to the file system
within the tx_begin() call or using the tx_add() call
lazily after the transaction creation. After that, all related file
operations before the end of the transaction belongs to this
transaction. To finish the transaction, tx_commit() will
make all related updates durable, and tx_abort() will undo
all operations related to this transaction. Note that FCFS does
not alter the existing file I/O interfaces within a transaction,
which largely simplifies the use of its APIs.
Figure 1 shows a simple example in C of how to use FCFS
to atomically update two files. The program first opens the two
files. It then starts a new transaction using the tx_begin()
call. The parameter info in tx_begin() specifies two file
descriptors (fd1 and fd2) that belong to this transaction.
Next, it performs two file write operations. It makes both
write operations durable with the tx_commit() call. The
atomicity that tx_commit() provides guarantees that either
both writes become durable, or neither does.
C. Overview
The goal of FCFS is to enforce application’s failure consis-
tency at the file system level with both correctness and high
performance. To this end, we propose an NVMM-optimized
WAL (NoW) scheme, which consists of two key techniques.
• Hybrid Fine-grained Logging (HFL), which decouples
the file system metadata log and data log, to avoid the
false sharing of log data and achieve a good tradeoff
between the copy cost and log tracking overhead.
• Concurrently Selective Checkpointing (CSC), where com-
mitted updates to different data blocks are checkpointed
concurrently to enhance the concurrency of checkpoint-
ing, while committed updates of the same data block
F C F P
...
C F P F
...
Running
Transaction
Free Metadata
Log Entries
Free Data
Log Entries
SB Metadata Log
Data Log Data and Pending Blocks
DRAM
NVMM
F – Free Log_entry/Block C – Committed Log_entry
P – Pending Log_entry/Block D – Data Block
D P F P
...
Committed
Transactions
– Linked List Head
– Linked Point
FCFS-Log
F
Free
Blocks
Fig. 2. FCFS Data Layout and Transaction Data Structures.
Pending
Log
Entry
Free
Log
Entry
Transaction
Commit
Transaction
Checkpointed
Transaction Begin
Transaction Abort
Committed
Log Entry
(a) Log Entry
Pending
Block
Data
Block
Free
Block
Transaction
Checkpointed
Transaction Begin
Transaction Abort/Checkpointed
(b) Data Block
Fig. 3. State Diagrams of Log Entry and Data Block in FCFS.
are carefully handled using the Selective Checkpointing
Algorithm to ensure correctness and reduce unnecessary
data copies.
FCFS Layout. FCFS data layout is shown in Figure 2.
The superblock is followed by a journal (FCFS-Log) and
dynamically allocated blocks. FCFS-Log is further divided
into two areas, metadata log and data log, for different
objectives. We will discuss the FCFS-Log layout in detail in
Section III-D.
Allocator. Data allocation in FCFS is block-oriented, while
journal allocation is based on the data structure (i.e., log entry).
To avoid high logging and ordering overhead, FCFS maintains
all the allocator structures in volatile memory using free lists
rather than journaling them to the costly NVMM storage. The
consistency of allocators will be discussed in Section III-F.
Creating and running transactions. When applications
call tx_begin(), FCFS will create a new transaction and
assign a transaction ID (TxID). In a running transaction,
instead of directly overwriting the old-version data in the
persistent data blocks, either old data (undo) or new data
(redo) is journaled first to protect the old-version data. When
a free log entry or block is allocated to this transaction, its
state will change from Free to Pending (Figure 3). To ensure