731 lines · 21.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0+/* * linux/fs/jbd2/checkpoint.c * * Written by Stephen C. Tweedie <sct@redhat.com>, 1999 * * Copyright 1999 Red Hat Software --- All Rights Reserved * * Checkpoint routines for the generic filesystem journaling code. * Part of the ext2fs journaling system. * * Checkpointing is the process of ensuring that a section of the log is * committed fully to disk, so that that portion of the log can be * reused. */ #include <linux/time.h>#include <linux/fs.h>#include <linux/jbd2.h>#include <linux/errno.h>#include <linux/slab.h>#include <linux/blkdev.h>#include <trace/events/jbd2.h> /* * Unlink a buffer from a transaction checkpoint list. * * Called with j_list_lock held. */static inline void __buffer_unlink(struct journal_head *jh){	transaction_t *transaction = jh->b_cp_transaction; 	jh->b_cpnext->b_cpprev = jh->b_cpprev;	jh->b_cpprev->b_cpnext = jh->b_cpnext;	if (transaction->t_checkpoint_list == jh) {		transaction->t_checkpoint_list = jh->b_cpnext;		if (transaction->t_checkpoint_list == jh)			transaction->t_checkpoint_list = NULL;	}} /* * __jbd2_log_wait_for_space: wait until there is space in the journal. * * Called under j-state_lock *only*.  It will be unlocked if we have to wait * for a checkpoint to free up some space in the log. */void __jbd2_log_wait_for_space(journal_t *journal)__acquires(&journal->j_state_lock)__releases(&journal->j_state_lock){	int nblocks, space_left;	/* assert_spin_locked(&journal->j_state_lock); */ 	nblocks = journal->j_max_transaction_buffers;	while (jbd2_log_space_left(journal) < nblocks) {		write_unlock(&journal->j_state_lock);		mutex_lock_io(&journal->j_checkpoint_mutex); 		/*		 * Test again, another process may have checkpointed while we		 * were waiting for the checkpoint lock. If there are no		 * transactions ready to be checkpointed, try to recover		 * journal space by calling cleanup_journal_tail(), and if		 * that doesn't work, by waiting for the currently committing		 * transaction to complete.  If there is absolutely no way		 * to make progress, this is either a BUG or corrupted		 * filesystem, so abort the journal and leave a stack		 * trace for forensic evidence.		 */		write_lock(&journal->j_state_lock);		if (journal->j_flags & JBD2_ABORT) {			mutex_unlock(&journal->j_checkpoint_mutex);			return;		}		spin_lock(&journal->j_list_lock);		space_left = jbd2_log_space_left(journal);		if (space_left < nblocks) {			int chkpt = journal->j_checkpoint_transactions != NULL;			tid_t tid = 0;			bool has_transaction = false; 			if (journal->j_committing_transaction) {				tid = journal->j_committing_transaction->t_tid;				has_transaction = true;			}			spin_unlock(&journal->j_list_lock);			write_unlock(&journal->j_state_lock);			if (chkpt) {				jbd2_log_do_checkpoint(journal);			} else if (jbd2_cleanup_journal_tail(journal) <= 0) {				/*				 * We were able to recover space or the				 * journal was aborted due to an error.				 */				;			} else if (has_transaction) {				/*				 * jbd2_journal_commit_transaction() may want				 * to take the checkpoint_mutex if JBD2_FLUSHED				 * is set.  So we need to temporarily drop it.				 */				mutex_unlock(&journal->j_checkpoint_mutex);				jbd2_log_wait_commit(journal, tid);				write_lock(&journal->j_state_lock);				continue;			} else {				printk(KERN_ERR "%s: needed %d blocks and "				       "only had %d space available\n",				       __func__, nblocks, space_left);				printk(KERN_ERR "%s: no way to get more "				       "journal space in %s\n", __func__,				       journal->j_devname);				WARN_ON(1);				jbd2_journal_abort(journal, -ENOSPC);			}			write_lock(&journal->j_state_lock);		} else {			spin_unlock(&journal->j_list_lock);		}		mutex_unlock(&journal->j_checkpoint_mutex);	}} static void__flush_batch(journal_t *journal, int *batch_count){	int i;	struct blk_plug plug; 	blk_start_plug(&plug);	for (i = 0; i < *batch_count; i++)		write_dirty_buffer(journal->j_chkpt_bhs[i], JBD2_JOURNAL_REQ_FLAGS);	blk_finish_plug(&plug); 	for (i = 0; i < *batch_count; i++) {		struct buffer_head *bh = journal->j_chkpt_bhs[i];		BUFFER_TRACE(bh, "brelse");		__brelse(bh);		journal->j_chkpt_bhs[i] = NULL;	}	*batch_count = 0;} /* * Perform an actual checkpoint. We take the first transaction on the * list of transactions to be checkpointed and send all its buffers * to disk. We submit larger chunks of data at once. * * The journal should be locked before calling this function. * Called with j_checkpoint_mutex held. */int jbd2_log_do_checkpoint(journal_t *journal){	struct journal_head	*jh;	struct buffer_head	*bh;	transaction_t		*transaction;	tid_t			this_tid;	int			result, batch_count = 0; 	jbd2_debug(1, "Start checkpoint\n"); 	/*	 * First thing: if there are any transactions in the log which	 * don't need checkpointing, just eliminate them from the	 * journal straight away.	 */	result = jbd2_cleanup_journal_tail(journal);	trace_jbd2_checkpoint(journal, result);	jbd2_debug(1, "cleanup_journal_tail returned %d\n", result);	if (result <= 0)		return result; 	/*	 * OK, we need to start writing disk blocks.  Take one transaction	 * and write it.	 */	spin_lock(&journal->j_list_lock);	if (!journal->j_checkpoint_transactions)		goto out;	transaction = journal->j_checkpoint_transactions;	if (transaction->t_chp_stats.cs_chp_time == 0)		transaction->t_chp_stats.cs_chp_time = jiffies;	this_tid = transaction->t_tid;restart:	/*	 * If someone cleaned up this transaction while we slept, we're	 * done (maybe it's a new transaction, but it fell at the same	 * address).	 */	if (journal->j_checkpoint_transactions != transaction ||	    transaction->t_tid != this_tid)		goto out; 	/* checkpoint all of the transaction's buffers */	while (transaction->t_checkpoint_list) {		jh = transaction->t_checkpoint_list;		bh = jh2bh(jh); 		if (jh->b_transaction != NULL) {			transaction_t *t = jh->b_transaction;			tid_t tid = t->t_tid; 			transaction->t_chp_stats.cs_forced_to_close++;			spin_unlock(&journal->j_list_lock);			if (unlikely(journal->j_flags & JBD2_UNMOUNT))				/*				 * The journal thread is dead; so				 * starting and waiting for a commit				 * to finish will cause us to wait for				 * a _very_ long time.				 */				printk(KERN_ERR		"JBD2: %s: Waiting for Godot: block %llu\n",		journal->j_devname, (unsigned long long) bh->b_blocknr); 			if (batch_count)				__flush_batch(journal, &batch_count);			jbd2_log_start_commit(journal, tid);			/*			 * jbd2_journal_commit_transaction() may want			 * to take the checkpoint_mutex if JBD2_FLUSHED			 * is set, jbd2_update_log_tail() called by			 * jbd2_journal_commit_transaction() may also take			 * checkpoint_mutex.  So we need to temporarily			 * drop it.			 */			mutex_unlock(&journal->j_checkpoint_mutex);			jbd2_log_wait_commit(journal, tid);			mutex_lock_io(&journal->j_checkpoint_mutex);			spin_lock(&journal->j_list_lock);			goto restart;		}		if (!trylock_buffer(bh)) {			/*			 * The buffer is locked, it may be writing back, or			 * flushing out in the last couple of cycles, or			 * re-adding into a new transaction, need to check			 * it again until it's unlocked.			 */			get_bh(bh);			spin_unlock(&journal->j_list_lock);			wait_on_buffer(bh);			/* the journal_head may have gone by now */			BUFFER_TRACE(bh, "brelse");			__brelse(bh);			goto retry;		} else if (!buffer_dirty(bh)) {			unlock_buffer(bh);			BUFFER_TRACE(bh, "remove from checkpoint");			/*			 * If the transaction was released or the checkpoint			 * list was empty, we're done.			 */			if (__jbd2_journal_remove_checkpoint(jh) ||			    !transaction->t_checkpoint_list)				goto out;		} else {			unlock_buffer(bh);			/*			 * We are about to write the buffer, it could be			 * raced by some other transaction shrink or buffer			 * re-log logic once we release the j_list_lock,			 * leave it on the checkpoint list and check status			 * again to make sure it's clean.			 */			BUFFER_TRACE(bh, "queue");			get_bh(bh);			if (WARN_ON_ONCE(buffer_jwrite(bh))) {				put_bh(bh); /* drop the ref we just took */				spin_unlock(&journal->j_list_lock);				/* Clean up any previously batched buffers */				if (batch_count)					__flush_batch(journal, &batch_count);				jbd2_journal_abort(journal, -EFSCORRUPTED);				return -EFSCORRUPTED;			}			journal->j_chkpt_bhs[batch_count++] = bh;			transaction->t_chp_stats.cs_written++;			transaction->t_checkpoint_list = jh->b_cpnext;		} 		if ((batch_count == JBD2_NR_BATCH) ||		    need_resched() || spin_needbreak(&journal->j_list_lock) ||		    jh2bh(transaction->t_checkpoint_list) == journal->j_chkpt_bhs[0])			goto unlock_and_flush;	} 	if (batch_count) {		unlock_and_flush:			spin_unlock(&journal->j_list_lock);		retry:			if (batch_count)				__flush_batch(journal, &batch_count);			cond_resched();			spin_lock(&journal->j_list_lock);			goto restart;	} out:	spin_unlock(&journal->j_list_lock);	result = jbd2_cleanup_journal_tail(journal); 	return (result < 0) ? result : 0;} /* * Check the list of checkpoint transactions for the journal to see if * we have already got rid of any since the last update of the log tail * in the journal superblock.  If so, we can instantly roll the * superblock forward to remove those transactions from the log. * * Return <0 on error, 0 on success, 1 if there was nothing to clean up. * * Called with the journal lock held. * * This is the only part of the journaling code which really needs to be * aware of transaction aborts.  Checkpointing involves writing to the * main filesystem area rather than to the journal, so it can proceed * even in abort state, but we must not update the super block if * checkpointing may have failed.  Otherwise, we would lose some metadata * buffers which should be written-back to the filesystem. */ int jbd2_cleanup_journal_tail(journal_t *journal){	tid_t		first_tid;	unsigned long	blocknr; 	if (is_journal_aborted(journal))		return -EIO; 	if (!jbd2_journal_get_log_tail(journal, &first_tid, &blocknr))		return 1;	if (WARN_ON_ONCE(blocknr == 0)) {		jbd2_journal_abort(journal, -EFSCORRUPTED);		return -EFSCORRUPTED;	} 	/*	 * We need to make sure that any blocks that were recently written out	 * --- perhaps by jbd2_log_do_checkpoint() --- are flushed out before	 * we drop the transactions from the journal. It's unlikely this will	 * be necessary, especially with an appropriately sized journal, but we	 * need this to guarantee correctness.  Fortunately	 * jbd2_cleanup_journal_tail() doesn't get called all that often.	 */	if (journal->j_flags & JBD2_BARRIER)		blkdev_issue_flush(journal->j_fs_dev); 	return __jbd2_update_log_tail(journal, first_tid, blocknr);}  /* Checkpoint list management */ /* * journal_shrink_one_cp_list * * Find written-back checkpoint buffers in the given list and try to release * them. If 'nr_to_scan' is set, scan at most that many buffers. If the whole * transaction is released, set the 'released' parameter. Return the number of * released checkpointed buffers. * * Called with j_list_lock held. */static unsigned long journal_shrink_one_cp_list(struct journal_head *jh,						enum jbd2_shrink_type type,						unsigned long *nr_to_scan,						bool *released){	struct journal_head *last_jh;	struct journal_head *next_jh = jh;	unsigned long nr_freed = 0;	int ret; 	*released = false;	if (!jh || (nr_to_scan && !*nr_to_scan))		return 0; 	last_jh = jh->b_cpprev;	do {		jh = next_jh;		next_jh = jh->b_cpnext;		if (nr_to_scan)			(*nr_to_scan)--; 		if (type == JBD2_SHRINK_DESTROY) {			ret = __jbd2_journal_remove_checkpoint(jh);		} else {			ret = jbd2_journal_try_remove_checkpoint(jh);			if (ret < 0) {				if (type == JBD2_SHRINK_BUSY_SKIP)					goto next;				break;			}		} 		nr_freed++;		if (ret) {			*released = true;			break;		} next:		if (need_resched())			break;	} while (jh != last_jh && (!nr_to_scan || *nr_to_scan)); 	return nr_freed;} /* * jbd2_journal_shrink_checkpoint_list * * Find 'nr_to_scan' written-back checkpoint buffers in the journal * and try to release them. Return the number of released checkpointed * buffers. * * Called with j_list_lock held. */unsigned long jbd2_journal_shrink_checkpoint_list(journal_t *journal,						  unsigned long *nr_to_scan){	transaction_t *transaction, *last_transaction, *next_transaction;	bool __maybe_unused released;	tid_t first_tid = 0, last_tid = 0, next_tid = 0;	tid_t tid = 0;	unsigned long nr_freed = 0;	bool first_set = false; again:	spin_lock(&journal->j_list_lock);	if (!journal->j_checkpoint_transactions) {		spin_unlock(&journal->j_list_lock);		goto out;	} 	/*	 * Get next shrink transaction, resume previous scan or start	 * over again. If some others do checkpoint and drop transaction	 * from the checkpoint list, we ignore saved j_shrink_transaction	 * and start over unconditionally.	 */	if (journal->j_shrink_transaction)		transaction = journal->j_shrink_transaction;	else		transaction = journal->j_checkpoint_transactions; 	if (!first_set) {		first_tid = transaction->t_tid;		first_set = true;	}	last_transaction = journal->j_checkpoint_transactions->t_cpprev;	next_transaction = transaction;	last_tid = last_transaction->t_tid;	do {		transaction = next_transaction;		next_transaction = transaction->t_cpnext;		tid = transaction->t_tid; 		nr_freed += journal_shrink_one_cp_list(transaction->t_checkpoint_list,						       JBD2_SHRINK_BUSY_SKIP,						       nr_to_scan, &released);		if (*nr_to_scan == 0)			break;		if (need_resched() || spin_needbreak(&journal->j_list_lock))			break;	} while (transaction != last_transaction); 	if (transaction != last_transaction) {		journal->j_shrink_transaction = next_transaction;		next_tid = next_transaction->t_tid;	} else {		journal->j_shrink_transaction = NULL;		next_tid = 0;	} 	spin_unlock(&journal->j_list_lock);	cond_resched(); 	if (*nr_to_scan && journal->j_shrink_transaction)		goto again;out:	trace_jbd2_shrink_checkpoint_list(journal, first_tid, tid, last_tid,					  nr_freed, next_tid); 	return nr_freed;} /* * journal_clean_checkpoint_list * * Find all the written-back checkpoint buffers in the journal and release them. * If 'type' is JBD2_SHRINK_DESTROY, release all buffers unconditionally. If * 'type' is JBD2_SHRINK_BUSY_STOP, will stop release buffers if encounters a * busy buffer. To avoid wasting CPU cycles scanning the buffer list in some * cases, don't pass JBD2_SHRINK_BUSY_SKIP 'type' for this function. *

Lines 1–500 of 731.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t