changeset 402:675a2af6ea46

work in progress
author Charles Manning <cdhmanning@gmail.com>
date Fri, 16 Apr 2010 12:13:27 +1200
parents 7d524c984100
children 205fa93db422
files linux-tests/gather_data.sh linux-tests/mkfiles yaffs_fs.c yaffs_guts.c yaffs_guts.h
diffstat 5 files changed, 323 insertions(+), 175 deletions(-) [+]
line wrap: on
line diff
--- a/linux-tests/gather_data.sh
+++ b/linux-tests/gather_data.sh
@@ -10,5 +10,5 @@ str=$(cat /proc/yaffs_debug)
 echo "$i, $str" 
 echo "$i, $str"  >> $the_file
 let i=$i+1
-sleep 1
+sleep 0.5
 done
--- a/linux-tests/mkfiles
+++ b/linux-tests/mkfiles
@@ -2,12 +2,12 @@
 
 TREE_BASE=/mnt/dirtree
 
-for (( f=0; f<50; f++ ))
+for (( f=0; f<500; f++ ))
 do
-	for (( d=0; d<50; d++ ))
+	for (( d=0; d<10; d++ ))
 	do
 		mkdir -p $TREE_BASE/dir$d
-		dd bs=1024 count=20 if=/dev/zero of=$TREE_BASE/dir$d/file$f
+		dd bs=1024 count=5 if=/dev/zero of=$TREE_BASE/dir$d/file$f
 	done
 done
 
--- a/yaffs_fs.c
+++ b/yaffs_fs.c
@@ -69,6 +69,7 @@ extern const char *yaffs_guts_c_version;
 #ifdef YAFFS_COMPILE_BACKGROUND
 #include <linux/kthread.h>
 #include <linux/delay.h>
+#include <linux/freezer.h>
 #endif
 
 
@@ -1976,26 +1977,84 @@ static int yaffs_do_sync_fs(struct super
  */
 
 #ifdef YAFFS_COMPILE_BACKGROUND
+
+void yaffs_background_waker(unsigned long data)
+{
+	wake_up_process((struct task_struct *)data);
+}
+
 static int yaffs_BackgroundThread(void *data)
 {
 	yaffs_Device *dev = (yaffs_Device *)data;
 	struct yaffs_LinuxContext *context = yaffs_DeviceToContext(dev);
+	unsigned long now = jiffies;
+	unsigned long next_dir_update = now;
+	unsigned long next_gc = now;
+	unsigned long expires;
+
+	int gcResult;
+	unsigned int erasedChunks;
+	struct timer_list timer;
 
 	T(YAFFS_TRACE_BACKGROUND,
 		("yaffs_background starting for dev %p\n",
 		(void *)dev));
-	
+
+	set_freezable();
+
 	while(context->bgRunning){
 		T(YAFFS_TRACE_BACKGROUND,
 			("yaffs_background\n"));
 
 		if(kthread_should_stop())
 			break;
+
+		if(try_to_freeze())
+			continue;
+
 		yaffs_GrossLock(dev);
-		yaffs_UpdateDirtyDirectories(dev);
+
+		now = jiffies;
+
+		if(time_after(now, next_dir_update) &&
+			!dev->isCheckpointed){
+			yaffs_UpdateDirtyDirectories(dev);
+			next_dir_update = now + HZ;
+		}
+
+		if(time_after(now,next_gc) &&
+			! dev->isCheckpointed){
+			gcResult = yaffs_BackgroundGarbageCollect(dev);
+			erasedChunks = dev->nErasedBlocks * dev->param.nChunksPerBlock;
+			if(erasedChunks < dev->nFreeChunks/4)
+				next_gc = now + HZ/50+1;
+			else if(erasedChunks < dev->nFreeChunks/2)
+				next_gc = now + HZ/20+1;
+			else
+				next_gc = now + HZ * 2;
+		}
 		yaffs_GrossUnlock(dev);
-		msleep(500);
+#if 1
+		expires = next_dir_update;
+		if (time_before(next_gc,expires))
+			expires = next_gc;
+		if(time_before(expires,now))
+			expires = now + HZ;
+
+		init_timer(&timer);
+		timer.expires = expires+1;
+		timer.data = (unsigned long) current;
+		timer.function = yaffs_background_waker;
+
+                set_current_state(TASK_INTERRUPTIBLE);
+		add_timer(&timer);
+		schedule();
+		del_timer_sync(&timer);
+#else
+		msleep(10);
+#endif
 	}
+
 	return 0;
 }
 
@@ -2173,6 +2232,10 @@ static void yaffs_put_super(struct super
 
 	T(YAFFS_TRACE_OS, ("yaffs_put_super\n"));
 
+	T(YAFFS_TRACE_OS | YAFFS_TRACE_BACKGROUND, ("Shutting down yaffs background thread\n"));
+	yaffs_BackgroundStop(dev);
+	T(YAFFS_TRACE_OS | YAFFS_TRACE_BACKGROUND, ("yaffs background thread shut down\n"));
+
 	yaffs_GrossLock(dev);
 
 	yaffs_FlushSuperBlock(sb,1);
@@ -2180,7 +2243,6 @@ static void yaffs_put_super(struct super
 	if (yaffs_DeviceToContext(dev)->putSuperFunc)
 		yaffs_DeviceToContext(dev)->putSuperFunc(sb);
 
-	yaffs_BackgroundStop(dev);
 
 	yaffs_Deinitialise(dev);
 
@@ -2545,7 +2607,7 @@ static struct super_block *yaffs_interna
 #ifdef CONFIG_YAFFS_DISABLE_BLOCK_REFRESHING
 	param->refreshPeriod = 0;
 #else
-	param->refreshPeriod = 10000;
+	param->refreshPeriod = 100;
 #endif
 
 	if(options.empty_lost_and_found_overridden)
@@ -3133,6 +3195,7 @@ static void __exit exit_yaffs_fs(void)
 			       " removing. \n"));
 
 	remove_proc_entry("yaffs", YPROC_ROOT);
+	remove_proc_entry("yaffs_debug", YPROC_ROOT);
 
 	fsinst = fs_to_install;
 
--- a/yaffs_guts.c
+++ b/yaffs_guts.c
@@ -34,7 +34,9 @@ const char *yaffs_guts_c_version =
 #include "yaffs_packedtags2.h"
 
 
-#define YAFFS_PASSIVE_GC_CHUNKS 2
+/* Note YAFFS_GC_GOOD_ENOUGH must be <= YAFFS_GC_PASSIVE_THRESHOLD */
+#define YAFFS_GC_GOOD_ENOUGH 2
+#define YAFFS_GC_PASSIVE_THRESHOLD 4
 #define YAFFS_SMALL_HOLE_THRESHOLD 3
 
 #include "yaffs_ecc.h"
@@ -1058,41 +1060,41 @@ static int yaffs_WriteNewChunkWithTagsTo
  * yaffs_FindOldestDirtySequence()
  * Calculate the oldest dirty sequence number if we don't know it.
  */
-static int yaffs_CalcOldestDirtySequence(yaffs_Device *dev)
+static void yaffs_CalcOldestDirtySequence(yaffs_Device *dev)
 {
 	int i;
-	__u32 seq;
+	unsigned seq;
+	unsigned blockNo = 0;
 	yaffs_BlockInfo *b;
 
 	if(!dev->param.isYaffs2)
-		return 0;
+		return;
 
 	/* Find the oldest dirty sequence number. */
-	seq = dev->sequenceNumber;
+	seq = dev->sequenceNumber + 1;
 	b = dev->blockInfo;
 	for (i = dev->internalStartBlock; i <= dev->internalEndBlock; i++) {
 		if (b->blockState == YAFFS_BLOCK_STATE_FULL &&
-		    (b->pagesInUse - b->softDeletions) < dev->param.nChunksPerBlock &&
-		    b->sequenceNumber < seq) 
-				seq = b->sequenceNumber;
+			(b->pagesInUse - b->softDeletions) < dev->param.nChunksPerBlock &&
+			b->sequenceNumber < seq) {
+			seq = b->sequenceNumber;
+			blockNo = i;
+		}
 		b++;
 	}
-	return seq;
+
+	if(blockNo){
+		dev->oldestDirtySequence = seq;
+		dev->oldestDirtyBlock = blockNo;
+	}
+
 }
 
 
 static void yaffs_FindOldestDirtySequence(yaffs_Device *dev)
 {
 	if(dev->param.isYaffs2 && !dev->oldestDirtySequence)
-		dev->oldestDirtySequence = 
-			yaffs_CalcOldestDirtySequence(dev);
-
-#if 0
-	if(!yaffs_SkipVerification(dev) &&
-		dev->oldestDirtySequence != yaffs_CalcOldestDirtySequence(dev))
-		YBUG();
-
-#endif
+		yaffs_CalcOldestDirtySequence(dev);
 }
 
 /*
@@ -1107,8 +1109,10 @@ static void yaffs_ClearOldestDirtySequen
 	if(!dev->param.isYaffs2)
 		return;
 
-	if(!bi || bi->sequenceNumber == dev->oldestDirtySequence)
+	if(!bi || bi->sequenceNumber == dev->oldestDirtySequence){
 		dev->oldestDirtySequence = 0;
+		dev->oldestDirtyBlock = 0;
+	}
 }
 
 /*
@@ -1116,11 +1120,13 @@ static void yaffs_ClearOldestDirtySequen
  * Update the oldest dirty sequence number whenever we dirty a block.
  * Only do this if the oldestDirtySequence is actually being tracked.
  */
-static void yaffs_UpdateOldestDirtySequence(yaffs_Device *dev, yaffs_BlockInfo *bi)
+static void yaffs_UpdateOldestDirtySequence(yaffs_Device *dev, unsigned blockNo, yaffs_BlockInfo *bi)
 {
 	if(dev->param.isYaffs2 && dev->oldestDirtySequence){
-		if(dev->oldestDirtySequence > bi->sequenceNumber)
+		if(dev->oldestDirtySequence > bi->sequenceNumber){
 			dev->oldestDirtySequence = bi->sequenceNumber;
+			dev->oldestDirtyBlock = blockNo;
+		}
 	}
 }
  
@@ -1799,14 +1805,16 @@ static int yaffs_DeleteWorker(yaffs_Obje
 static void yaffs_SoftDeleteChunk(yaffs_Device *dev, int chunk)
 {
 	yaffs_BlockInfo *theBlock;
+	unsigned blockNo;
 
 	T(YAFFS_TRACE_DELETION, (TSTR("soft delete chunk %d" TENDSTR), chunk));
 
-	theBlock = yaffs_GetBlockInfo(dev, chunk / dev->param.nChunksPerBlock);
+	blockNo =  chunk / dev->param.nChunksPerBlock;
+	theBlock = yaffs_GetBlockInfo(dev, blockNo);
 	if (theBlock) {
 		theBlock->softDeletions++;
 		dev->nFreeChunks++;
-		yaffs_UpdateOldestDirtySequence(dev,theBlock);
+		yaffs_UpdateOldestDirtySequence(dev, blockNo, theBlock);
 	}
 }
 
@@ -2667,12 +2675,12 @@ int yaffs_RenameObject(yaffs_Object *old
 			 * Note we must disable gc otherwise it can mess up the shadowing.
 			 *
 			 */
-			dev->isDoingGC=1;
+			dev->gcDisable=1;
 			yaffs_ChangeObjectName(obj, newDir, newName, force,
 						existingTarget->objectId);
 			existingTarget->isShadowed = 1;
 			yaffs_UnlinkObject(existingTarget);
-			dev->isDoingGC=0;
+			dev->gcDisable=0;
 		}
 
 		result = yaffs_ChangeObjectName(obj, newDir, newName, 1, 0);
@@ -2789,10 +2797,8 @@ static __u32 yaffs_FindRefreshBlock(yaff
         if(dev->refreshSkip > dev->param.refreshPeriod)
                 dev->refreshSkip = dev->param.refreshPeriod;
 
-	if(dev->refreshSkip > 0){
-	        dev->refreshSkip--;
+	if(dev->refreshSkip > 0)
 	        return oldest;
-	}
 
 	/*
 	 * Refresh skip is now zero.
@@ -2824,106 +2830,6 @@ static __u32 yaffs_FindRefreshBlock(yaff
 	return oldest;
 }
 
-/*
- * FindDiretiestBlock is used to select the dirtiest block (or close enough)
- * for garbage collection.
- */
-
-static int yaffs_FindBlockForGarbageCollection(yaffs_Device *dev,
-					int aggressive)
-{
-	int b = dev->currentDirtyChecker;
-
-	int i;
-	int iterations;
-	int dirtiest = -1;
-	int pagesInUse = 0;
-	int prioritised = 0;
-	yaffs_BlockInfo *bi;
-	int pendingPrioritisedExist = 0;
-
-	/* First let's see if we need to grab a prioritised block */
-	if (dev->hasPendingPrioritisedGCs) {
-		bi = dev->blockInfo;
-		for (i = dev->internalStartBlock; i < dev->internalEndBlock && !prioritised; i++) {
-
-			if (bi->gcPrioritise) {
-				pendingPrioritisedExist = 1;
-				if (bi->blockState == YAFFS_BLOCK_STATE_FULL &&
-				   yaffs_BlockNotDisqualifiedFromGC(dev, bi)) {
-					pagesInUse = (bi->pagesInUse - bi->softDeletions);
-					dirtiest = i;
-					prioritised = 1;
-					aggressive = 1; /* Fool the non-aggressive skip logiv below */
-				}
-			}
-			bi++;
-		}
-
-		if (!pendingPrioritisedExist) /* None found, so we can clear this */
-			dev->hasPendingPrioritisedGCs = 0;
-	}
-
-	/* If we're doing aggressive GC then we are happy to take a less-dirty block, and
-	 * search harder.
-	 * else (we're doing a leasurely gc), then we only bother to do this if the
-	 * block has only a few pages in use.
-	 */
-
-	dev->nonAggressiveSkip--;
-
-	if (!aggressive && (dev->nonAggressiveSkip > 0))
-		return -1;
-
-	if (!prioritised)
-		pagesInUse =
-			(aggressive) ? dev->param.nChunksPerBlock : YAFFS_PASSIVE_GC_CHUNKS + 1;
-
-	if (aggressive)
-		iterations =
-		    dev->internalEndBlock - dev->internalStartBlock + 1;
-	else {
-		iterations =
-		    dev->internalEndBlock - dev->internalStartBlock + 1;
-		iterations = iterations / 16;
-		if (iterations > 200)
-			iterations = 200;
-	}
-
-	for (i = 0; i <= iterations && pagesInUse > 0 && !prioritised; i++) {
-		b++;
-		if (b < dev->internalStartBlock || b > dev->internalEndBlock)
-			b = dev->internalStartBlock;
-
-		if (b < dev->internalStartBlock || b > dev->internalEndBlock) {
-			T(YAFFS_TRACE_ERROR,
-			  (TSTR("**>> Block %d is not valid" TENDSTR), b));
-			YBUG();
-		}
-
-		bi = yaffs_GetBlockInfo(dev, b);
-
-		if (bi->blockState == YAFFS_BLOCK_STATE_FULL &&
-			(bi->pagesInUse - bi->softDeletions) < pagesInUse &&
-				yaffs_BlockNotDisqualifiedFromGC(dev, bi)) {
-			dirtiest = b;
-			pagesInUse = (bi->pagesInUse - bi->softDeletions);
-		}
-	}
-
-	dev->currentDirtyChecker = b;
-
-	if (dirtiest > 0) {
-		T(YAFFS_TRACE_GC,
-		  (TSTR("GC Selected block %d with %d free, prioritised:%d" TENDSTR), dirtiest,
-		   dev->param.nChunksPerBlock - pagesInUse, prioritised));
-	}
-
-	if (dirtiest > 0)
-		dev->nonAggressiveSkip = 4;
-
-	return dirtiest;
-}
 
 static void yaffs_BlockBecameDirty(yaffs_Device *dev, int blockNo)
 {
@@ -2945,7 +2851,13 @@ static void yaffs_BlockBecameDirty(yaffs
 
 	/* If this is the block being garbage collected then stop gc'ing this block */
 	if(blockNo == dev->gcBlock)
-		dev->gcBlock = -1;
+		dev->gcBlock = 0;
+
+	/* If this block is currently the best candidate for gc then drop as a candidate */
+	if(blockNo == dev->gcDirtiest){
+		dev->gcDirtiest = 0;
+		dev->gcPagesInUse = 0;
+	}
 
 	if (!bi->needsRetiring) {
 		yaffs_InvalidateCheckpoint(dev);
@@ -3219,7 +3131,7 @@ static int yaffs_GarbageCollectBlock(yaf
 	
 	bi->hasShrinkHeader = 0;	/* clear the flag so that the block can erase */
 
-	dev->isDoingGC = 1;
+	dev->gcDisable = 1;
 
 	if (isCheckpointBlock ||
 			!yaffs_StillSomeChunkBits(dev, block)) {
@@ -3234,7 +3146,7 @@ static int yaffs_GarbageCollectBlock(yaf
 
 		yaffs_VerifyBlock(dev, bi, block);
 
-		maxCopies = (wholeBlock) ? dev->param.nChunksPerBlock : 10;
+		maxCopies = (wholeBlock) ? dev->param.nChunksPerBlock : 5;
 		oldChunk = block * dev->param.nChunksPerBlock + dev->gcChunk;
 
 		for (/* init already done */;
@@ -3440,15 +3352,154 @@ static int yaffs_GarbageCollectBlock(yaf
 			   ("gc did not increase free chunks before %d after %d"
 			    TENDSTR), chunksBefore, chunksAfter));
 		}
-		dev->gcBlock = -1;
+		dev->gcBlock = 0;
 		dev->gcChunk = 0;
 	}
 
-	dev->isDoingGC = 0;
+	dev->gcDisable = 0;
 
 	return retVal;
 }
 
+/*
+ * FindBlockForgarbageCollection is used to select the dirtiest block (or close enough)
+ * for garbage collection.
+ */
+
+static unsigned yaffs_FindBlockForGarbageCollection(yaffs_Device *dev,
+					int aggressive,
+					int background)
+{
+	int i;
+	int iterations;
+	unsigned selected = 0;
+	int prioritised = 0;
+	int prioritisedExists = 0;
+	yaffs_BlockInfo *bi;
+	int threshold;
+
+	/* First let's see if we need to grab a prioritised block */
+	if (dev->hasPendingPrioritisedGCs && !aggressive) {
+		dev->gcDirtiest = 0;
+		bi = dev->blockInfo;
+		for (i = dev->internalStartBlock;
+			i <= dev->internalEndBlock && !selected;
+			i++) {
+
+			if (bi->gcPrioritise) {
+				prioritisedExists = 1;
+				if (bi->blockState == YAFFS_BLOCK_STATE_FULL &&
+				   yaffs_BlockNotDisqualifiedFromGC(dev, bi)) {
+					selected = i;
+					prioritised = 1;
+				}
+			}
+			bi++;
+		}
+
+		/*
+		 * If there is a prioritised block and none was selected then
+		 * this happened because there is at least one old dirty block gumming
+		 * up the works. Let's gc the oldest dirty block.
+		 */
+
+		if(prioritisedExists &&
+			!selected &&
+			dev->oldestDirtyBlock > 0)
+			selected = dev->oldestDirtyBlock;
+
+		if (!prioritisedExists) /* None found, so we can clear this */
+			dev->hasPendingPrioritisedGCs = 0;
+	}
+
+	/* If we're doing aggressive GC then we are happy to take a less-dirty block, and
+	 * search harder.
+	 * else (we're doing a leasurely gc), then we only bother to do this if the
+	 * block has only a few pages in use.
+	 */
+
+	if (!selected){
+		int pagesUsed;
+		int nBlocks = dev->internalEndBlock - dev->internalStartBlock + 1;
+		if (aggressive){
+			threshold = dev->param.nChunksPerBlock;
+			iterations = nBlocks;
+		} else {
+			int maxThreshold = dev->param.nChunksPerBlock/2;
+			threshold = background ?
+				(dev->gcNotDone + 2) * 2 : 0;
+			threshold = max(threshold, YAFFS_GC_PASSIVE_THRESHOLD);
+			threshold = min(threshold, maxThreshold);
+
+			iterations = nBlocks / 16 + 1;
+			if (iterations > 100)
+				iterations = 100;
+		}
+
+		for (i = 0;
+			i < iterations &&
+			(dev->gcDirtiest < 1 ||
+				dev->gcPagesInUse > YAFFS_GC_GOOD_ENOUGH);
+			i++) {
+			dev->gcBlockFinder++;
+			if (dev->gcBlockFinder < dev->internalStartBlock ||
+				dev->gcBlockFinder > dev->internalEndBlock)
+				dev->gcBlockFinder = dev->internalStartBlock;
+
+			bi = yaffs_GetBlockInfo(dev, dev->gcBlockFinder);
+
+			pagesUsed = bi->pagesInUse - bi->softDeletions;
+
+			if (bi->blockState == YAFFS_BLOCK_STATE_FULL &&
+				pagesUsed < dev->param.nChunksPerBlock &&
+				(dev->gcDirtiest < 1 || pagesUsed < dev->gcPagesInUse) &&
+				yaffs_BlockNotDisqualifiedFromGC(dev, bi)) {
+				dev->gcDirtiest = dev->gcBlockFinder;
+				dev->gcPagesInUse = pagesUsed;
+			}
+		}
+
+		if(dev->gcDirtiest > 0 && dev->gcPagesInUse <= threshold)
+			selected = dev->gcDirtiest;
+	}
+
+	if(!selected && dev->param.isYaffs2 && dev->gcNotDone >= ( background ? 10 : 20)){
+		yaffs_FindOldestDirtySequence(dev);
+		if(dev->oldestDirtyBlock > 0) {
+			selected = dev->oldestDirtyBlock;
+			dev->gcDirtiest = selected;
+			bi = yaffs_GetBlockInfo(dev, selected);
+			dev->gcPagesInUse =  bi->pagesInUse - bi->softDeletions;
+		} else
+			dev->gcNotDone = 0;
+	}
+
+	if(selected){
+		T(YAFFS_TRACE_GC,
+		  (TSTR("GC Selected block %d with %d free, prioritised:%d" TENDSTR),
+		  selected,
+		  dev->param.nChunksPerBlock - dev->gcPagesInUse,
+		  prioritised));
+
+		dev->gcDirtiest = 0;
+		dev->gcPagesInUse = 0;
+		dev->gcNotDone = 0;
+		if(dev->refreshSkip > 0)
+			dev->refreshSkip--;
+	} else{
+		dev->gcNotDone++;
+		T(YAFFS_TRACE_GC,
+		  (TSTR("GC none: finder %d skip %d threshold %d dirtiest %d using %d oldest %d%s" TENDSTR),
+		  dev->gcBlockFinder, dev->gcNotDone,
+		  threshold,
+		  dev->gcDirtiest, dev->gcPagesInUse,
+		  dev->oldestDirtyBlock,
+		  background ? " bg" : ""));
+	}
+
+	return selected;
+}
+
 /* New garbage collector
  * If we're very low on erased blocks then we do aggressive garbage collection
  * otherwise we do "leasurely" garbage collection.
@@ -3458,26 +3509,28 @@ static int yaffs_GarbageCollectBlock(yaf
  * The idea is to help clear out space in a more spread-out manner.
  * Dunno if it really does anything useful.
  */
-static int yaffs_CheckGarbageCollection(yaffs_Device *dev)
-{
-	int block;
-	int aggressive;
+static int yaffs_CheckGarbageCollection(yaffs_Device *dev, int background)
+{
+	int aggressive = 0;
 	int gcOk = YAFFS_OK;
 	int maxTries = 0;
 
+	int minErased;
+	int erasedChunks;
+
 	int checkpointBlockAdjust;
 
 	if(dev->param.gcControl &&
 		(dev->param.gcControl(dev) & 1) == 0)
 		return YAFFS_OK;
 
-	if (dev->isDoingGC) {
+	if (dev->gcDisable) {
 		/* Bail out so we don't get recursive gc */
 		return YAFFS_OK;
 	}
 
 	/* This loop should pass the first time.
-	 * We'll only see looping here if the erase of the collected block fails.
+	 * We'll only see looping here if the collection does not increase space.
 	 */
 
 	do {
@@ -3487,11 +3540,26 @@ static int yaffs_CheckGarbageCollection(
 		if (checkpointBlockAdjust < 0)
 			checkpointBlockAdjust = 0;
 
+		minErased  = dev->param.nReservedBlocks + checkpointBlockAdjust + 1;
+		erasedChunks = dev->nErasedBlocks * dev->param.nChunksPerBlock;
+
 		/* If we need a block soon then do aggressive gc.*/
-		if (dev->nErasedBlocks < (dev->param.nReservedBlocks + checkpointBlockAdjust + 2))
+		if (dev->nErasedBlocks < minErased)
 			aggressive = 1;
-		else
-			aggressive = 0;
+		else {
+			if(dev->gcSkip > 20)
+				dev->gcSkip = 20;
+			if(erasedChunks < dev->nFreeChunks/2 ||
+				dev->gcSkip < 1 ||
+				background)
+				aggressive = 0;
+			else {
+				dev->gcSkip--;
+				break;
+			}
+		}
+
+		dev->gcSkip = 5;
 
                 /* If we don't already have a block being gc'd then see if we should start another */
 
@@ -3500,13 +3568,11 @@ static int yaffs_CheckGarbageCollection(
 			dev->gcChunk = 0;
 		}
 		if (dev->gcBlock < 1) {
-			dev->gcBlock = yaffs_FindBlockForGarbageCollection(dev, aggressive);
+			dev->gcBlock = yaffs_FindBlockForGarbageCollection(dev, aggressive, background);
 			dev->gcChunk = 0;
 		}
 
-		block = dev->gcBlock;
-
-		if (block > 0) {
+		if (dev->gcBlock > 0) {
 			dev->garbageCollections++;
 			if (!aggressive)
 				dev->passiveGarbageCollections++;
@@ -3516,22 +3582,37 @@ static int yaffs_CheckGarbageCollection(
 			   ("yaffs: GC erasedBlocks %d aggressive %d" TENDSTR),
 			   dev->nErasedBlocks, aggressive));
 
-			gcOk = yaffs_GarbageCollectBlock(dev, block, aggressive);
+			gcOk = yaffs_GarbageCollectBlock(dev, dev->gcBlock, aggressive);
 		}
 
-		if (dev->nErasedBlocks < (dev->param.nReservedBlocks) && block > 0) {
+		if (dev->nErasedBlocks < (dev->param.nReservedBlocks) && dev->gcBlock > 0) {
 			T(YAFFS_TRACE_GC,
 			  (TSTR
 			   ("yaffs: GC !!!no reclaim!!! erasedBlocks %d after try %d block %d"
-			    TENDSTR), dev->nErasedBlocks, maxTries, block));
+			    TENDSTR), dev->nErasedBlocks, maxTries, dev->gcBlock));
 		}
 	} while ((dev->nErasedBlocks < dev->param.nReservedBlocks) &&
-		 (block > 0) &&
+		 (dev->gcBlock > 0) &&
 		 (maxTries < 2));
 
 	return aggressive ? gcOk : YAFFS_OK;
 }
 
+/*
+ * yaffs_BackgroundGarbageCollect()
+ * Garbage collects. Intended to be called from a background thread.
+ * Returns non-zero if at least half the free chunks are erased.
+ */
+int yaffs_BackgroundGarbageCollect(yaffs_Device *dev)
+{
+	int erasedChunks = dev->nErasedBlocks * dev->param.nChunksPerBlock;
+
+	T(YAFFS_TRACE_BACKGROUND, (TSTR("Background gc" TENDSTR)));
+
+	yaffs_CheckGarbageCollection(dev, 1);
+	return erasedChunks > dev->nFreeChunks/2;
+}
+
 /*-------------------------  TAGS --------------------------------*/
 
 static int yaffs_TagsMatch(const yaffs_ExtendedTags *tags, int objectId,
@@ -3835,7 +3916,7 @@ void yaffs_DeleteChunk(yaffs_Device *dev
 
 	bi = yaffs_GetBlockInfo(dev, block);
 	
-	yaffs_UpdateOldestDirtySequence(dev,bi);
+	yaffs_UpdateOldestDirtySequence(dev, block, bi);
 
 	T(YAFFS_TRACE_DELETION,
 	  (TSTR("line %d delete of chunk %d" TENDSTR), lyn, chunkId));
@@ -3894,7 +3975,7 @@ static int yaffs_WriteChunkDataToObject(
 
 	yaffs_Device *dev = in->myDev;
 
-	yaffs_CheckGarbageCollection(dev);
+	yaffs_CheckGarbageCollection(dev,0);
 
 	/* Get the previous chunk at this location in the file if it exists.
 	 * If it does not exist then put a zero into the tree. This creates
@@ -3969,7 +4050,7 @@ int yaffs_UpdateObjectHeader(yaffs_Objec
 		in == dev->rootDir || /* The rootDir should also be saved */
 		force) {
 
-		yaffs_CheckGarbageCollection(dev);
+		yaffs_CheckGarbageCollection(dev,0);
 		yaffs_CheckObjectDetailsLoaded(in);
 
 		buffer = yaffs_GetTempBuffer(in->myDev, __LINE__);
@@ -5379,7 +5460,7 @@ int yaffs_ResizeFile(yaffs_Object *in, l
 	yaffs_FlushFilesChunkCache(in);
 	yaffs_InvalidateWholeChunkCache(in);
 
-	yaffs_CheckGarbageCollection(dev);
+	yaffs_CheckGarbageCollection(dev,0);
 
 	if (in->variantType != YAFFS_OBJECT_TYPE_FILE)
 		return YAFFS_FAIL;
@@ -7695,7 +7776,7 @@ int yaffs_GutsInitialise(yaffs_Device *d
 	dev->chunkOffset = 0;
 	dev->nFreeChunks = 0;
 
-	dev->gcBlock = -1;
+	dev->gcBlock = 0;
 
 	if (dev->param.startBlock == 0) {
 		dev->internalStartBlock = dev->param.startBlock + 1;
@@ -7826,7 +7907,7 @@ int yaffs_GutsInitialise(yaffs_Device *d
 	/* More device initialisation */
 	dev->garbageCollections = 0;
 	dev->passiveGarbageCollections = 0;
-	dev->currentDirtyChecker = 0;
+	dev->gcBlockFinder = 0;
 	dev->bufferedBlock = -1;
 	dev->doingBufferedBlockRewrite = 0;
 	dev->nDeletedFiles = 0;
@@ -7838,10 +7919,11 @@ int yaffs_GutsInitialise(yaffs_Device *d
 	dev->tagsEccUnfixed = 0;
 	dev->nErasureFailures = 0;
 	dev->nErasedBlocks = 0;
-	dev->isDoingGC = 0;
+	dev->gcDisable= 0;
 	dev->hasPendingPrioritisedGCs = 1; /* Assume the worst for now, will get fixed on first GC */
 	YINIT_LIST_HEAD(&dev->dirtyDirectories);
 	dev->oldestDirtySequence = 0;
+	dev->oldestDirtyBlock = 0;
 
 	/* Initialise temporary buffers and caches. */
 	if (!yaffs_InitialiseTempBuffers(dev))
--- a/yaffs_guts.h
+++ b/yaffs_guts.h
@@ -689,10 +689,6 @@ struct yaffs_DeviceStruct {
 	int nFreeTnodes;
 	yaffs_TnodeList *allocatedTnodeList;
 
-	int isDoingGC;
-	int gcBlock;
-	int gcChunk;
-
 	int nObjectsCreated;
 	yaffs_Object *freeObjects;
 	int nFreeObjects;
@@ -706,12 +702,18 @@ struct yaffs_DeviceStruct {
 
 	int nFreeChunks;
 
-	int currentDirtyChecker;	/* Used to find current dirtiest block */
-
+	/* Garbage collection control */
 	__u32 *gcCleanupList;	/* objects to delete at the end of a GC. */
-	int nonAggressiveSkip;	/* GC state/mode */
 
-	int hasPendingPrioritisedGCs; /* We think this device might have pending prioritised gcs */
+	unsigned hasPendingPrioritisedGCs; /* We think this device might have pending prioritised gcs */
+	unsigned gcDisable;
+	unsigned gcBlockFinder;
+	unsigned gcDirtiest;
+	unsigned gcPagesInUse;
+	unsigned gcNotDone;
+	unsigned gcBlock;
+	unsigned gcChunk;
+	unsigned gcSkip;
 
 	/* Special directories */
 	yaffs_Object *rootDir;
@@ -746,6 +748,7 @@ struct yaffs_DeviceStruct {
 	/* yaffs2 runtime stuff */
 	unsigned sequenceNumber;	/* Sequence number of currently allocating block */
 	unsigned oldestDirtySequence;
+	unsigned oldestDirtyBlock;
 
 	/* Block refreshing */
 	int refreshSkip;	/* A skip down counter. Refresh happens when this gets to zero. */
@@ -892,9 +895,9 @@ void yfsd_WinFileTimeNow(__u32 target[2]
 
 void yaffs_HandleDeferedFree(yaffs_Object *obj);
 
-
 void yaffs_UpdateDirtyDirectories(yaffs_Device *dev);
 
+int yaffs_BackgroundGarbageCollect(yaffs_Device *dev);
 
 /* Debug dump  */
 int yaffs_DumpObject(yaffs_Object *obj);