Commit e06c8227 authored by Joel Becker's avatar Joel Becker Committed by Mark Fasheh

jbd2: Add buffer triggers

Filesystems often to do compute intensive operation on some
metadata.  If this operation is repeated many times, it can be very
expensive.  It would be much nicer if the operation could be performed
once before a buffer goes to disk.

This adds triggers to jbd2 buffer heads.  Just before writing a metadata
buffer to the journal, jbd2 will optionally call a commit trigger associated
with the buffer.  If the journal is aborted, an abort trigger will be
called on any dirty buffers as they are dropped from pending
transactions.

ocfs2 will use this feature.

Initially I tried to come up with a more generic trigger that could be
used for non-buffer-related events like transaction completion.  It
doesn't tie nicely, because the information a buffer trigger needs
(specific to a journal_head) isn't the same as what a transaction
trigger needs (specific to a tranaction_t or perhaps journal_t).  So I
implemented a buffer set, with the understanding that
journal/transaction wide triggers should be implemented separately.

There is only one trigger set allowed per buffer.  I can't think of any
reason to attach more than one set.  Contrast this with a journal or
transaction in which multiple places may want to watch the entire
transaction separately.

The trigger sets are considered static allocation from the jbd2
perspective.  ocfs2 will just have one trigger set per block type,
setting the same set on every bh of the same type.
Signed-off-by: default avatarJoel Becker <joel.becker@oracle.com>
Cc: "Theodore Ts'o" <tytso@mit.edu>
Cc: <linux-ext4@vger.kernel.org>
Signed-off-by: default avatarMark Fasheh <mfasheh@suse.com>
parent 754938c1
...@@ -509,6 +509,10 @@ void jbd2_journal_commit_transaction(journal_t *journal) ...@@ -509,6 +509,10 @@ void jbd2_journal_commit_transaction(journal_t *journal)
if (is_journal_aborted(journal)) { if (is_journal_aborted(journal)) {
clear_buffer_jbddirty(jh2bh(jh)); clear_buffer_jbddirty(jh2bh(jh));
JBUFFER_TRACE(jh, "journal is aborting: refile"); JBUFFER_TRACE(jh, "journal is aborting: refile");
jbd2_buffer_abort_trigger(jh,
jh->b_frozen_data ?
jh->b_frozen_triggers :
jh->b_triggers);
jbd2_journal_refile_buffer(journal, jh); jbd2_journal_refile_buffer(journal, jh);
/* If that was the last one, we need to clean up /* If that was the last one, we need to clean up
* any descriptor buffers which may have been * any descriptor buffers which may have been
...@@ -844,6 +848,9 @@ void jbd2_journal_commit_transaction(journal_t *journal) ...@@ -844,6 +848,9 @@ void jbd2_journal_commit_transaction(journal_t *journal)
* data. * data.
* *
* Otherwise, we can just throw away the frozen data now. * Otherwise, we can just throw away the frozen data now.
*
* We also know that the frozen data has already fired
* its triggers if they exist, so we can clear that too.
*/ */
if (jh->b_committed_data) { if (jh->b_committed_data) {
jbd2_free(jh->b_committed_data, bh->b_size); jbd2_free(jh->b_committed_data, bh->b_size);
...@@ -851,10 +858,12 @@ void jbd2_journal_commit_transaction(journal_t *journal) ...@@ -851,10 +858,12 @@ void jbd2_journal_commit_transaction(journal_t *journal)
if (jh->b_frozen_data) { if (jh->b_frozen_data) {
jh->b_committed_data = jh->b_frozen_data; jh->b_committed_data = jh->b_frozen_data;
jh->b_frozen_data = NULL; jh->b_frozen_data = NULL;
jh->b_frozen_triggers = NULL;
} }
} else if (jh->b_frozen_data) { } else if (jh->b_frozen_data) {
jbd2_free(jh->b_frozen_data, bh->b_size); jbd2_free(jh->b_frozen_data, bh->b_size);
jh->b_frozen_data = NULL; jh->b_frozen_data = NULL;
jh->b_frozen_triggers = NULL;
} }
spin_lock(&journal->j_list_lock); spin_lock(&journal->j_list_lock);
......
...@@ -50,6 +50,7 @@ EXPORT_SYMBOL(jbd2_journal_unlock_updates); ...@@ -50,6 +50,7 @@ EXPORT_SYMBOL(jbd2_journal_unlock_updates);
EXPORT_SYMBOL(jbd2_journal_get_write_access); EXPORT_SYMBOL(jbd2_journal_get_write_access);
EXPORT_SYMBOL(jbd2_journal_get_create_access); EXPORT_SYMBOL(jbd2_journal_get_create_access);
EXPORT_SYMBOL(jbd2_journal_get_undo_access); EXPORT_SYMBOL(jbd2_journal_get_undo_access);
EXPORT_SYMBOL(jbd2_journal_set_triggers);
EXPORT_SYMBOL(jbd2_journal_dirty_metadata); EXPORT_SYMBOL(jbd2_journal_dirty_metadata);
EXPORT_SYMBOL(jbd2_journal_release_buffer); EXPORT_SYMBOL(jbd2_journal_release_buffer);
EXPORT_SYMBOL(jbd2_journal_forget); EXPORT_SYMBOL(jbd2_journal_forget);
...@@ -290,6 +291,7 @@ int jbd2_journal_write_metadata_buffer(transaction_t *transaction, ...@@ -290,6 +291,7 @@ int jbd2_journal_write_metadata_buffer(transaction_t *transaction,
struct page *new_page; struct page *new_page;
unsigned int new_offset; unsigned int new_offset;
struct buffer_head *bh_in = jh2bh(jh_in); struct buffer_head *bh_in = jh2bh(jh_in);
struct jbd2_buffer_trigger_type *triggers;
/* /*
* The buffer really shouldn't be locked: only the current committing * The buffer really shouldn't be locked: only the current committing
...@@ -314,12 +316,22 @@ int jbd2_journal_write_metadata_buffer(transaction_t *transaction, ...@@ -314,12 +316,22 @@ int jbd2_journal_write_metadata_buffer(transaction_t *transaction,
done_copy_out = 1; done_copy_out = 1;
new_page = virt_to_page(jh_in->b_frozen_data); new_page = virt_to_page(jh_in->b_frozen_data);
new_offset = offset_in_page(jh_in->b_frozen_data); new_offset = offset_in_page(jh_in->b_frozen_data);
triggers = jh_in->b_frozen_triggers;
} else { } else {
new_page = jh2bh(jh_in)->b_page; new_page = jh2bh(jh_in)->b_page;
new_offset = offset_in_page(jh2bh(jh_in)->b_data); new_offset = offset_in_page(jh2bh(jh_in)->b_data);
triggers = jh_in->b_triggers;
} }
mapped_data = kmap_atomic(new_page, KM_USER0); mapped_data = kmap_atomic(new_page, KM_USER0);
/*
* Fire any commit trigger. Do this before checking for escaping,
* as the trigger may modify the magic offset. If a copy-out
* happens afterwards, it will have the correct data in the buffer.
*/
jbd2_buffer_commit_trigger(jh_in, mapped_data + new_offset,
triggers);
/* /*
* Check for escaping * Check for escaping
*/ */
...@@ -352,6 +364,13 @@ int jbd2_journal_write_metadata_buffer(transaction_t *transaction, ...@@ -352,6 +364,13 @@ int jbd2_journal_write_metadata_buffer(transaction_t *transaction,
new_page = virt_to_page(tmp); new_page = virt_to_page(tmp);
new_offset = offset_in_page(tmp); new_offset = offset_in_page(tmp);
done_copy_out = 1; done_copy_out = 1;
/*
* This isn't strictly necessary, as we're using frozen
* data for the escaping, but it keeps consistency with
* b_frozen_data usage.
*/
jh_in->b_frozen_triggers = jh_in->b_triggers;
} }
/* /*
......
...@@ -741,6 +741,12 @@ do_get_write_access(handle_t *handle, struct journal_head *jh, ...@@ -741,6 +741,12 @@ do_get_write_access(handle_t *handle, struct journal_head *jh,
source = kmap_atomic(page, KM_USER0); source = kmap_atomic(page, KM_USER0);
memcpy(jh->b_frozen_data, source+offset, jh2bh(jh)->b_size); memcpy(jh->b_frozen_data, source+offset, jh2bh(jh)->b_size);
kunmap_atomic(source, KM_USER0); kunmap_atomic(source, KM_USER0);
/*
* Now that the frozen data is saved off, we need to store
* any matching triggers.
*/
jh->b_frozen_triggers = jh->b_triggers;
} }
jbd_unlock_bh_state(bh); jbd_unlock_bh_state(bh);
...@@ -943,6 +949,47 @@ int jbd2_journal_get_undo_access(handle_t *handle, struct buffer_head *bh) ...@@ -943,6 +949,47 @@ int jbd2_journal_get_undo_access(handle_t *handle, struct buffer_head *bh)
return err; return err;
} }
/**
* void jbd2_journal_set_triggers() - Add triggers for commit writeout
* @bh: buffer to trigger on
* @type: struct jbd2_buffer_trigger_type containing the trigger(s).
*
* Set any triggers on this journal_head. This is always safe, because
* triggers for a committing buffer will be saved off, and triggers for
* a running transaction will match the buffer in that transaction.
*
* Call with NULL to clear the triggers.
*/
void jbd2_journal_set_triggers(struct buffer_head *bh,
struct jbd2_buffer_trigger_type *type)
{
struct journal_head *jh = bh2jh(bh);
jh->b_triggers = type;
}
void jbd2_buffer_commit_trigger(struct journal_head *jh, void *mapped_data,
struct jbd2_buffer_trigger_type *triggers)
{
struct buffer_head *bh = jh2bh(jh);
if (!triggers || !triggers->t_commit)
return;
triggers->t_commit(triggers, bh, mapped_data, bh->b_size);
}
void jbd2_buffer_abort_trigger(struct journal_head *jh,
struct jbd2_buffer_trigger_type *triggers)
{
if (!triggers || !triggers->t_abort)
return;
triggers->t_abort(triggers, jh2bh(jh));
}
/** /**
* int jbd2_journal_dirty_metadata() - mark a buffer as containing dirty metadata * int jbd2_journal_dirty_metadata() - mark a buffer as containing dirty metadata
* @handle: transaction to add buffer to. * @handle: transaction to add buffer to.
......
...@@ -1008,6 +1008,35 @@ int __jbd2_journal_clean_checkpoint_list(journal_t *journal); ...@@ -1008,6 +1008,35 @@ int __jbd2_journal_clean_checkpoint_list(journal_t *journal);
int __jbd2_journal_remove_checkpoint(struct journal_head *); int __jbd2_journal_remove_checkpoint(struct journal_head *);
void __jbd2_journal_insert_checkpoint(struct journal_head *, transaction_t *); void __jbd2_journal_insert_checkpoint(struct journal_head *, transaction_t *);
/*
* Triggers
*/
struct jbd2_buffer_trigger_type {
/*
* Fired just before a buffer is written to the journal.
* mapped_data is a mapped buffer that is the frozen data for
* commit.
*/
void (*t_commit)(struct jbd2_buffer_trigger_type *type,
struct buffer_head *bh, void *mapped_data,
size_t size);
/*
* Fired during journal abort for dirty buffers that will not be
* committed.
*/
void (*t_abort)(struct jbd2_buffer_trigger_type *type,
struct buffer_head *bh);
};
extern void jbd2_buffer_commit_trigger(struct journal_head *jh,
void *mapped_data,
struct jbd2_buffer_trigger_type *triggers);
extern void jbd2_buffer_abort_trigger(struct journal_head *jh,
struct jbd2_buffer_trigger_type *triggers);
/* Buffer IO */ /* Buffer IO */
extern int extern int
jbd2_journal_write_metadata_buffer(transaction_t *transaction, jbd2_journal_write_metadata_buffer(transaction_t *transaction,
...@@ -1046,6 +1075,8 @@ extern int jbd2_journal_extend (handle_t *, int nblocks); ...@@ -1046,6 +1075,8 @@ extern int jbd2_journal_extend (handle_t *, int nblocks);
extern int jbd2_journal_get_write_access(handle_t *, struct buffer_head *); extern int jbd2_journal_get_write_access(handle_t *, struct buffer_head *);
extern int jbd2_journal_get_create_access (handle_t *, struct buffer_head *); extern int jbd2_journal_get_create_access (handle_t *, struct buffer_head *);
extern int jbd2_journal_get_undo_access(handle_t *, struct buffer_head *); extern int jbd2_journal_get_undo_access(handle_t *, struct buffer_head *);
void jbd2_journal_set_triggers(struct buffer_head *,
struct jbd2_buffer_trigger_type *type);
extern int jbd2_journal_dirty_metadata (handle_t *, struct buffer_head *); extern int jbd2_journal_dirty_metadata (handle_t *, struct buffer_head *);
extern void jbd2_journal_release_buffer (handle_t *, struct buffer_head *); extern void jbd2_journal_release_buffer (handle_t *, struct buffer_head *);
extern int jbd2_journal_forget (handle_t *, struct buffer_head *); extern int jbd2_journal_forget (handle_t *, struct buffer_head *);
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
typedef unsigned int tid_t; /* Unique transaction ID */ typedef unsigned int tid_t; /* Unique transaction ID */
typedef struct transaction_s transaction_t; /* Compound transaction type */ typedef struct transaction_s transaction_t; /* Compound transaction type */
struct buffer_head; struct buffer_head;
struct journal_head { struct journal_head {
...@@ -87,6 +89,12 @@ struct journal_head { ...@@ -87,6 +89,12 @@ struct journal_head {
* [j_list_lock] * [j_list_lock]
*/ */
struct journal_head *b_cpnext, *b_cpprev; struct journal_head *b_cpnext, *b_cpprev;
/* Trigger type */
struct jbd2_buffer_trigger_type *b_triggers;
/* Trigger type for the committing transaction's frozen data */
struct jbd2_buffer_trigger_type *b_frozen_triggers;
}; };
#endif /* JOURNAL_HEAD_H_INCLUDED */ #endif /* JOURNAL_HEAD_H_INCLUDED */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment