diff options
Diffstat (limited to 'fs/jbd2/checkpoint.c')
| -rw-r--r-- | fs/jbd2/checkpoint.c | 277 | 
1 files changed, 94 insertions, 183 deletions
diff --git a/fs/jbd2/checkpoint.c b/fs/jbd2/checkpoint.c index 51bd38da21cd..9ec91017a7f3 100644 --- a/fs/jbd2/checkpoint.c +++ b/fs/jbd2/checkpoint.c @@ -27,7 +27,7 @@   *   * Called with j_list_lock held.   */ -static inline void __buffer_unlink_first(struct journal_head *jh) +static inline void __buffer_unlink(struct journal_head *jh)  {  	transaction_t *transaction = jh->b_cp_transaction; @@ -41,45 +41,6 @@ static inline void __buffer_unlink_first(struct journal_head *jh)  }  /* - * Unlink a buffer from a transaction checkpoint(io) list. - * - * Called with j_list_lock held. - */ -static inline void __buffer_unlink(struct journal_head *jh) -{ -	transaction_t *transaction = jh->b_cp_transaction; - -	__buffer_unlink_first(jh); -	if (transaction->t_checkpoint_io_list == jh) { -		transaction->t_checkpoint_io_list = jh->b_cpnext; -		if (transaction->t_checkpoint_io_list == jh) -			transaction->t_checkpoint_io_list = NULL; -	} -} - -/* - * Move a buffer from the checkpoint list to the checkpoint io list - * - * Called with j_list_lock held - */ -static inline void __buffer_relink_io(struct journal_head *jh) -{ -	transaction_t *transaction = jh->b_cp_transaction; - -	__buffer_unlink_first(jh); - -	if (!transaction->t_checkpoint_io_list) { -		jh->b_cpnext = jh->b_cpprev = jh; -	} else { -		jh->b_cpnext = transaction->t_checkpoint_io_list; -		jh->b_cpprev = transaction->t_checkpoint_io_list->b_cpprev; -		jh->b_cpprev->b_cpnext = jh; -		jh->b_cpnext->b_cpprev = jh; -	} -	transaction->t_checkpoint_io_list = jh; -} - -/*   * Check a checkpoint buffer could be release or not.   *   * Requires j_list_lock @@ -183,6 +144,7 @@ __flush_batch(journal_t *journal, int *batch_count)  		struct buffer_head *bh = journal->j_chkpt_bhs[i];  		BUFFER_TRACE(bh, "brelse");  		__brelse(bh); +		journal->j_chkpt_bhs[i] = NULL;  	}  	*batch_count = 0;  } @@ -242,15 +204,6 @@ restart:  		jh = transaction->t_checkpoint_list;  		bh = jh2bh(jh); -		if (buffer_locked(bh)) { -			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; -		}  		if (jh->b_transaction != NULL) {  			transaction_t *t = jh->b_transaction;  			tid_t tid = t->t_tid; @@ -285,30 +238,50 @@ restart:  			spin_lock(&journal->j_list_lock);  			goto restart;  		} -		if (!buffer_dirty(bh)) { +		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 (__jbd2_journal_remove_checkpoint(jh)) -				/* The transaction was released; we're done */ +			/* +			 * 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; -			continue; +		} 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); +			J_ASSERT_BH(bh, !buffer_jwrite(bh)); +			journal->j_chkpt_bhs[batch_count++] = bh; +			transaction->t_chp_stats.cs_written++; +			transaction->t_checkpoint_list = jh->b_cpnext;  		} -		/* -		 * Important: we are about to write the buffer, and -		 * possibly block, while still holding the journal -		 * lock.  We cannot afford to let the transaction -		 * logic start messing around with this buffer before -		 * we write it to disk, as that would break -		 * recoverability. -		 */ -		BUFFER_TRACE(bh, "queue"); -		get_bh(bh); -		J_ASSERT_BH(bh, !buffer_jwrite(bh)); -		journal->j_chkpt_bhs[batch_count++] = bh; -		__buffer_relink_io(jh); -		transaction->t_chp_stats.cs_written++; +  		if ((batch_count == JBD2_NR_BATCH) || -		    need_resched() || -		    spin_needbreak(&journal->j_list_lock)) +		    need_resched() || spin_needbreak(&journal->j_list_lock) || +		    jh2bh(transaction->t_checkpoint_list) == journal->j_chkpt_bhs[0])  			goto unlock_and_flush;  	} @@ -322,38 +295,6 @@ restart:  			goto restart;  	} -	/* -	 * Now we issued all of the transaction's buffers, let's deal -	 * with the buffers that are out for I/O. -	 */ -restart2: -	/* Did somebody clean up the transaction in the meanwhile? */ -	if (journal->j_checkpoint_transactions != transaction || -	    transaction->t_tid != this_tid) -		goto out; - -	while (transaction->t_checkpoint_io_list) { -		jh = transaction->t_checkpoint_io_list; -		bh = jh2bh(jh); -		if (buffer_locked(bh)) { -			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); -			spin_lock(&journal->j_list_lock); -			goto restart2; -		} - -		/* -		 * Now in whatever state the buffer currently is, we -		 * know that it has been written out and so we can -		 * drop it from the list -		 */ -		if (__jbd2_journal_remove_checkpoint(jh)) -			break; -	}  out:  	spin_unlock(&journal->j_list_lock);  	result = jbd2_cleanup_journal_tail(journal); @@ -409,49 +350,9 @@ int jbd2_cleanup_journal_tail(journal_t *journal)  /* Checkpoint list management */  /* - * journal_clean_one_cp_list - * - * Find all the written-back checkpoint buffers in the given list and - * release them. If 'destroy' is set, clean all buffers unconditionally. - * - * Called with j_list_lock held. - * Returns 1 if we freed the transaction, 0 otherwise. - */ -static int journal_clean_one_cp_list(struct journal_head *jh, bool destroy) -{ -	struct journal_head *last_jh; -	struct journal_head *next_jh = jh; - -	if (!jh) -		return 0; - -	last_jh = jh->b_cpprev; -	do { -		jh = next_jh; -		next_jh = jh->b_cpnext; - -		if (!destroy && __cp_buffer_busy(jh)) -			return 0; - -		if (__jbd2_journal_remove_checkpoint(jh)) -			return 1; -		/* -		 * This function only frees up some memory -		 * if possible so we dont have an obligation -		 * to finish processing. Bail out if preemption -		 * requested: -		 */ -		if (need_resched()) -			return 0; -	} while (jh != last_jh); - -	return 0; -} - -/*   * journal_shrink_one_cp_list   * - * Find 'nr_to_scan' written-back checkpoint buffers in the given list + * Find all the written-back checkpoint buffers in the given list   * and try to release them. If the whole transaction is released, set   * the 'released' parameter. Return the number of released checkpointed   * buffers. @@ -459,15 +360,15 @@ static int journal_clean_one_cp_list(struct journal_head *jh, bool destroy)   * Called with j_list_lock held.   */  static unsigned long journal_shrink_one_cp_list(struct journal_head *jh, -						unsigned long *nr_to_scan, -						bool *released) +						bool destroy, bool *released)  {  	struct journal_head *last_jh;  	struct journal_head *next_jh = jh;  	unsigned long nr_freed = 0;  	int ret; -	if (!jh || *nr_to_scan == 0) +	*released = false; +	if (!jh)  		return 0;  	last_jh = jh->b_cpprev; @@ -475,12 +376,15 @@ static unsigned long journal_shrink_one_cp_list(struct journal_head *jh,  		jh = next_jh;  		next_jh = jh->b_cpnext; -		(*nr_to_scan)--; -		if (__cp_buffer_busy(jh)) -			continue; +		if (destroy) { +			ret = __jbd2_journal_remove_checkpoint(jh); +		} else { +			ret = jbd2_journal_try_remove_checkpoint(jh); +			if (ret < 0) +				continue; +		}  		nr_freed++; -		ret = __jbd2_journal_remove_checkpoint(jh);  		if (ret) {  			*released = true;  			break; @@ -488,7 +392,7 @@ static unsigned long journal_shrink_one_cp_list(struct journal_head *jh,  		if (need_resched())  			break; -	} while (jh != last_jh && *nr_to_scan); +	} while (jh != last_jh);  	return nr_freed;  } @@ -506,11 +410,11 @@ unsigned long jbd2_journal_shrink_checkpoint_list(journal_t *journal,  						  unsigned long *nr_to_scan)  {  	transaction_t *transaction, *last_transaction, *next_transaction; -	bool released; +	bool __maybe_unused released;  	tid_t first_tid = 0, last_tid = 0, next_tid = 0;  	tid_t tid = 0;  	unsigned long nr_freed = 0; -	unsigned long nr_scanned = *nr_to_scan; +	unsigned long freed;  again:  	spin_lock(&journal->j_list_lock); @@ -539,19 +443,11 @@ again:  		transaction = next_transaction;  		next_transaction = transaction->t_cpnext;  		tid = transaction->t_tid; -		released = false; - -		nr_freed += journal_shrink_one_cp_list(transaction->t_checkpoint_list, -						       nr_to_scan, &released); -		if (*nr_to_scan == 0) -			break; -		if (need_resched() || spin_needbreak(&journal->j_list_lock)) -			break; -		if (released) -			continue; -		nr_freed += journal_shrink_one_cp_list(transaction->t_checkpoint_io_list, -						       nr_to_scan, &released); +		freed = journal_shrink_one_cp_list(transaction->t_checkpoint_list, +						   false, &released); +		nr_freed += freed; +		(*nr_to_scan) -= min(*nr_to_scan, freed);  		if (*nr_to_scan == 0)  			break;  		if (need_resched() || spin_needbreak(&journal->j_list_lock)) @@ -572,9 +468,8 @@ again:  	if (*nr_to_scan && next_tid)  		goto again;  out: -	nr_scanned -= *nr_to_scan;  	trace_jbd2_shrink_checkpoint_list(journal, first_tid, tid, last_tid, -					  nr_freed, nr_scanned, next_tid); +					  nr_freed, next_tid);  	return nr_freed;  } @@ -590,7 +485,7 @@ out:  void __jbd2_journal_clean_checkpoint_list(journal_t *journal, bool destroy)  {  	transaction_t *transaction, *last_transaction, *next_transaction; -	int ret; +	bool released;  	transaction = journal->j_checkpoint_transactions;  	if (!transaction) @@ -601,8 +496,8 @@ void __jbd2_journal_clean_checkpoint_list(journal_t *journal, bool destroy)  	do {  		transaction = next_transaction;  		next_transaction = transaction->t_cpnext; -		ret = journal_clean_one_cp_list(transaction->t_checkpoint_list, -						destroy); +		journal_shrink_one_cp_list(transaction->t_checkpoint_list, +					   destroy, &released);  		/*  		 * This function only frees up some memory if possible so we  		 * dont have an obligation to finish processing. Bail out if @@ -610,23 +505,12 @@ void __jbd2_journal_clean_checkpoint_list(journal_t *journal, bool destroy)  		 */  		if (need_resched())  			return; -		if (ret) -			continue; -		/* -		 * It is essential that we are as careful as in the case of -		 * t_checkpoint_list with removing the buffer from the list as -		 * we can possibly see not yet submitted buffers on io_list -		 */ -		ret = journal_clean_one_cp_list(transaction-> -				t_checkpoint_io_list, destroy); -		if (need_resched()) -			return;  		/*  		 * Stop scanning if we couldn't free the transaction. This  		 * avoids pointless scanning of transactions which still  		 * weren't checkpointed.  		 */ -		if (!ret) +		if (!released)  			return;  	} while (transaction != last_transaction);  } @@ -705,7 +589,7 @@ int __jbd2_journal_remove_checkpoint(struct journal_head *jh)  	jbd2_journal_put_journal_head(jh);  	/* Is this transaction empty? */ -	if (transaction->t_checkpoint_list || transaction->t_checkpoint_io_list) +	if (transaction->t_checkpoint_list)  		return 0;  	/* @@ -737,6 +621,34 @@ int __jbd2_journal_remove_checkpoint(struct journal_head *jh)  }  /* + * Check the checkpoint buffer and try to remove it from the checkpoint + * list if it's clean. Returns -EBUSY if it is not clean, returns 1 if + * it frees the transaction, 0 otherwise. + * + * This function is called with j_list_lock held. + */ +int jbd2_journal_try_remove_checkpoint(struct journal_head *jh) +{ +	struct buffer_head *bh = jh2bh(jh); + +	if (!trylock_buffer(bh)) +		return -EBUSY; +	if (buffer_dirty(bh)) { +		unlock_buffer(bh); +		return -EBUSY; +	} +	unlock_buffer(bh); + +	/* +	 * Buffer is clean and the IO has finished (we held the buffer +	 * lock) so the checkpoint is done. We can safely remove the +	 * buffer from this transaction. +	 */ +	JBUFFER_TRACE(jh, "remove from checkpoint list"); +	return __jbd2_journal_remove_checkpoint(jh); +} + +/*   * journal_insert_checkpoint: put a committed buffer onto a checkpoint   * list so that we know when it is safe to clean the transaction out of   * the log. @@ -797,7 +709,6 @@ void __jbd2_journal_drop_transaction(journal_t *journal, transaction_t *transact  	J_ASSERT(transaction->t_forget == NULL);  	J_ASSERT(transaction->t_shadow_list == NULL);  	J_ASSERT(transaction->t_checkpoint_list == NULL); -	J_ASSERT(transaction->t_checkpoint_io_list == NULL);  	J_ASSERT(atomic_read(&transaction->t_updates) == 0);  	J_ASSERT(journal->j_committing_transaction != transaction);  	J_ASSERT(journal->j_running_transaction != transaction);  |