📄 transaction.c
字号:
/* * linux/fs/transaction.c * * Written by Stephen C. Tweedie <sct@redhat.com>, 1998 * * Copyright 1998 Red Hat corp --- All Rights Reserved * * This file is part of the Linux kernel and is made available under * the terms of the GNU General Public License, version 2, or at your * option, any later version, incorporated herein by reference. * * Generic filesystem transaction handling code; part of the ext2fs * journaling system. * * This file manages transactions (compound commits managed by the * journaling code) and handles (individual atomic operations by the * filesystem). */#include <linux/sched.h>#include <linux/fs.h>#include <linux/jbd.h>#include <linux/errno.h>#include <linux/slab.h>#include <linux/locks.h>#include <linux/timer.h>#include <linux/smp_lock.h>#include <linux/mm.h>extern spinlock_t journal_datalist_lock;/* * get_transaction: obtain a new transaction_t object. * * Simply allocate and initialise a new transaction. Create it in * RUNNING state and add it to the current journal (which should not * have an existing running transaction: we only make a new transaction * once we have started to commit the old one). * * Preconditions: * The journal MUST be locked. We don't perform atomic mallocs on the * new transaction and we can't block without protecting against other * processes trying to touch the journal while it is in transition. */static transaction_t * get_transaction (journal_t * journal, int is_try){ transaction_t * transaction; transaction = jbd_kmalloc (sizeof (transaction_t), GFP_NOFS); if (!transaction) return NULL; memset (transaction, 0, sizeof (transaction_t)); transaction->t_journal = journal; transaction->t_state = T_RUNNING; transaction->t_tid = journal->j_transaction_sequence++; transaction->t_expires = jiffies + journal->j_commit_interval; /* Set up the commit timer for the new transaction. */ J_ASSERT (!journal->j_commit_timer_active); journal->j_commit_timer_active = 1; journal->j_commit_timer->expires = transaction->t_expires; add_timer(journal->j_commit_timer); J_ASSERT (journal->j_running_transaction == NULL); journal->j_running_transaction = transaction; return transaction;}/* * Handle management. * * A handle_t is an object which represents a single atomic update to a * filesystem, and which tracks all of the modifications which form part * of that one update. *//* * start_this_handle: Given a handle, deal with any locking or stalling * needed to make sure that there is enough journal space for the handle * to begin. Attach the handle to a transaction and set up the * transaction's buffer credits. */static int start_this_handle(journal_t *journal, handle_t *handle){ transaction_t *transaction; int needed; int nblocks = handle->h_buffer_credits; jbd_debug(3, "New handle %p going live.\n", handle);repeat: lock_journal(journal);repeat_locked: if (is_journal_aborted(journal) || (journal->j_errno != 0 && !(journal->j_flags & JFS_ACK_ERR))) { unlock_journal(journal); return -EROFS; } /* Wait on the journal's transaction barrier if necessary */ if (journal->j_barrier_count) { unlock_journal(journal); sleep_on(&journal->j_wait_transaction_locked); goto repeat; } if (!journal->j_running_transaction) get_transaction(journal, 0); /* @@@ Error? */ J_ASSERT(journal->j_running_transaction); transaction = journal->j_running_transaction; /* If the current transaction is locked down for commit, wait * for the lock to be released. */ if (transaction->t_state == T_LOCKED) { unlock_journal(journal); jbd_debug(3, "Handle %p stalling...\n", handle); sleep_on(&journal->j_wait_transaction_locked); goto repeat; } /* If there is not enough space left in the log to write all * potential buffers requested by this operation, we need to * stall pending a log checkpoint to free some more log * space. */ needed = transaction->t_outstanding_credits + nblocks; if (needed > journal->j_max_transaction_buffers) { /* If the current transaction is already too large, then * start to commit it: we can then go back and attach * this handle to a new transaction. */ jbd_debug(2, "Handle %p starting new commit...\n", handle); log_start_commit(journal, transaction); unlock_journal(journal); sleep_on(&journal->j_wait_transaction_locked); lock_journal(journal); goto repeat_locked; } /* * The commit code assumes that it can get enough log space * without forcing a checkpoint. This is *critical* for * correctness: a checkpoint of a buffer which is also * associated with a committing transaction creates a deadlock, * so commit simply cannot force through checkpoints. * * We must therefore ensure the necessary space in the journal * *before* starting to dirty potentially checkpointed buffers * in the new transaction. * * The worst part is, any transaction currently committing can * reduce the free space arbitrarily. Be careful to account for * those buffers when checkpointing. */ /* * @@@ AKPM: This seems rather over-defensive. We're giving commit * a _lot_ of headroom: 1/4 of the journal plus the size of * the committing transaction. Really, we only need to give it * committing_transaction->t_outstanding_credits plus "enough" for * the log control blocks. * Also, this test is inconsitent with the matching one in * journal_extend(). */ needed = journal->j_max_transaction_buffers; if (journal->j_committing_transaction) needed += journal->j_committing_transaction-> t_outstanding_credits; if (log_space_left(journal) < needed) { jbd_debug(2, "Handle %p waiting for checkpoint...\n", handle); log_wait_for_space(journal, needed); goto repeat_locked; } /* OK, account for the buffers that this operation expects to * use and add the handle to the running transaction. */ handle->h_transaction = transaction; transaction->t_outstanding_credits += nblocks; transaction->t_updates++; transaction->t_handle_count++; jbd_debug(4, "Handle %p given %d credits (total %d, free %d)\n", handle, nblocks, transaction->t_outstanding_credits, log_space_left(journal)); unlock_journal(journal); return 0;}/* * Obtain a new handle. * * We make sure that the transaction can guarantee at least nblocks of * modified buffers in the log. We block until the log can guarantee * that much space. * * This function is visible to journal users (like ext2fs), so is not * called with the journal already locked. * * Return a pointer to a newly allocated handle, or NULL on failure */handle_t *journal_start(journal_t *journal, int nblocks){ handle_t *handle = journal_current_handle(); int err; if (!journal) return ERR_PTR(-EROFS); if (handle) { J_ASSERT(handle->h_transaction->t_journal == journal); handle->h_ref++; return handle; } handle = jbd_kmalloc(sizeof (handle_t), GFP_NOFS); if (!handle) return ERR_PTR(-ENOMEM); memset (handle, 0, sizeof (handle_t)); handle->h_buffer_credits = nblocks; handle->h_ref = 1; current->journal_info = handle; err = start_this_handle(journal, handle); if (err < 0) { kfree(handle); current->journal_info = NULL; return ERR_PTR(err); } return handle;}/* * Return zero on success */static int try_start_this_handle(journal_t *journal, handle_t *handle){ transaction_t *transaction; int needed; int nblocks = handle->h_buffer_credits; int ret = 0; jbd_debug(3, "New handle %p maybe going live.\n", handle); lock_journal(journal); if (is_journal_aborted(journal) || (journal->j_errno != 0 && !(journal->j_flags & JFS_ACK_ERR))) { ret = -EROFS; goto fail_unlock; } if (journal->j_barrier_count) goto fail_unlock; if (!journal->j_running_transaction && get_transaction(journal, 1) == 0) goto fail_unlock; transaction = journal->j_running_transaction; if (transaction->t_state == T_LOCKED) goto fail_unlock; needed = transaction->t_outstanding_credits + nblocks; /* We could run log_start_commit here */ if (needed > journal->j_max_transaction_buffers) goto fail_unlock; needed = journal->j_max_transaction_buffers; if (journal->j_committing_transaction) needed += journal->j_committing_transaction-> t_outstanding_credits; if (log_space_left(journal) < needed) goto fail_unlock; handle->h_transaction = transaction; transaction->t_outstanding_credits += nblocks; transaction->t_updates++; jbd_debug(4, "Handle %p given %d credits (total %d, free %d)\n", handle, nblocks, transaction->t_outstanding_credits, log_space_left(journal)); unlock_journal(journal); return 0;fail_unlock: unlock_journal(journal); if (ret >= 0) ret = -1; return ret;}/* * Try to start a handle, but non-blockingly. If we weren't able * to, return an ERR_PTR value. */handle_t *journal_try_start(journal_t *journal, int nblocks){ handle_t *handle = journal_current_handle(); int err; if (!journal) return ERR_PTR(-EROFS); if (handle) { jbd_debug(4, "h_ref %d -> %d\n", handle->h_ref, handle->h_ref + 1); J_ASSERT(handle->h_transaction->t_journal == journal); if (is_handle_aborted(handle)) return ERR_PTR(-EIO); handle->h_ref++; return handle; } else { jbd_debug(4, "no current transaction\n"); } if (is_journal_aborted(journal)) return ERR_PTR(-EIO); handle = jbd_kmalloc(sizeof (handle_t), GFP_NOFS); if (!handle) return ERR_PTR(-ENOMEM); memset (handle, 0, sizeof (handle_t)); handle->h_buffer_credits = nblocks; handle->h_ref = 1; current->journal_info = handle; err = try_start_this_handle(journal, handle); if (err < 0) { kfree(handle); current->journal_info = NULL; return ERR_PTR(err); } return handle;}/* * journal_extend: extend buffer credits. * * Some transactions, such as large extends and truncates, can be done * atomically all at once or in several stages. The operation requests * a credit for a number of buffer modications in advance, but can * extend its credit if it needs more. * * journal_extend tries to give the running handle more buffer credits. * It does not guarantee that allocation: this is a best-effort only. * The calling process MUST be able to deal cleanly with a failure to * extend here. * * Return 0 on success, non-zero on failure. * * return code < 0 implies an error * return code > 0 implies normal transaction-full status. */int journal_extend (handle_t *handle, int nblocks){ transaction_t *transaction = handle->h_transaction; journal_t *journal = transaction->t_journal; int result; int wanted; lock_journal (journal); result = -EIO; if (is_handle_aborted(handle)) goto error_out; result = 1; /* Don't extend a locked-down transaction! */ if (handle->h_transaction->t_state != T_RUNNING) { jbd_debug(3, "denied handle %p %d blocks: " "transaction not running\n", handle, nblocks); goto error_out; } wanted = transaction->t_outstanding_credits + nblocks; if (wanted > journal->j_max_transaction_buffers) { jbd_debug(3, "denied handle %p %d blocks: " "transaction too large\n", handle, nblocks); goto error_out; } if (wanted > log_space_left(journal)) { jbd_debug(3, "denied handle %p %d blocks: " "insufficient log space\n", handle, nblocks); goto error_out; } handle->h_buffer_credits += nblocks; transaction->t_outstanding_credits += nblocks;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -