How Linux Implements Ext3 Ordered Data Journaling

The ext3 file system's data=ordered mode provides a balance between file system performance and data integrity by guaranteeing that file data is written to the main storage disk before its associated metadata is committed to the journal. This design prevents a critical vulnerability present in writeback caching: pointers in newly committed metadata referencing unwritten, stale disk blocks after a system crash. In the Linux kernel, this mechanism is coordinated between the ext3 file system driver and the Journaling Block Device (JBD) subsystem using transaction lists and staged I/O flushes.

The Role of the Journaling Block Device (JBD)

The ext3 file system does not handle the raw mechanics of journaling internally. Instead, it delegates transaction management to the JBD layer. JBD organizes file system changes into discrete transactions. At any given time, JBD manages:

In data=ordered mode, JBD handles the sequence in which data buffers and metadata buffers move through these transaction states.

Associating Data Blocks with Transactions

When an application writes to a file in data=ordered mode:

  1. Allocating Blocks: The ext3 driver assigns logical blocks to physical blocks on the storage device.
  2. Attaching Data to the Transaction: Ext3 calls journal_dirty_data() to link the dirty page-cache buffers containing the file's payload to the running transaction's private data list (t_sync_datalist).
  3. Dirtying Metadata: The metadata modifying the inode, block allocation bitmaps, and indirect blocks is registered with the transaction using journal_get_write_access() and journal_dirty_metadata().

At this stage, the data is queued in system memory, explicitly tethered to the metadata that describes it.

The Commit Pipeline

A background kernel thread named kjournald is responsible for committing transactions to disk periodically (typically every 5 seconds) or when an explicit synchronization event occurs (such as an fsync() system call). The ordered implementation executes in strict sequential phases within the journal_commit_transaction() function:

  1. Phase 1: Flushing Ordered Data: Before any journal updates occur, kjournald traverses the transaction's t_sync_datalist. It initiates block I/O write requests for every data buffer attached to the transaction, writing the payload directly to its final destination on the file system.
  2. Phase 2: Waiting for Data I/O Completion: The thread blocks until the storage controller confirms that all data blocks from Phase 1 have been physically written to non-volatile storage.
  3. Phase 3: Writing Journal Descriptor and Metadata: Once the payload is safely on disk, JBD writes the transaction's metadata blocks to the journal area on the disk, preceded by a descriptor block that identifies the metadata.
  4. Phase 4: Writing the Commit Record: Finally, a single commit block is written to the journal. The presence of the commit block marks the transaction as legally complete.

Crash Recovery Guarantees

If power fails or the operating system crashes during this process, the recovery semantics ensure file consistency: