seems I was able to find a solution.
The newer versions of the linux kernel seems to have some changes in the ext2 and ext3 file system drivers. They skipped a check that made a routine that is trying to find new blocks to make a new directory go to the the end of a loop and then skipping some functions that needs cpu power and memory when the group of blocks under investigation was not suitable. Maybe at newer system with faster cpu's and more memory you will never notice it as a problem. Only it takes a little longer to create a directory on a big partition. In case of the spd8020 it caused problems, on ext3 an OOM error (as result of the journalling) and aborting the creation of the directory, and in ext2 a long delay in creating the directory.
The problem only happened on bigger partitions and when creating a sub directory outside the root of the partition. It then started with a slightly to high group number and then loop through all groups to find a good syuitable one. As there are 4000 groups this can take a while in case there are cpu and memory intensive check done for each group.
I added a check that was made in earlier versions of the kernel to skip a part of the function that takes time and memory when the group of blocks is not suitable, and that did the job, now it works ok in ext3 (no OOM error anymore) and creating the directory in less then a second.
The error is in the file balloc.c in fs/ext3 (and fs/ext2) folder. Below the part of the code with the fix, the fix is in line 117-120 (the lines in between the marks /* fvdw */, the function causing the OOM and long time delay is in line 137 (ext3_try_to_allocate_with_rsv). With this fix that part is skipped when the group is not suitable.
I have now a 2.6.33 kernel running on spd8020 (LS-LGL)...
Code:
ext3_fsblk_t ext3_new_blocks(handle_t *handle, struct inode *inode,
ext3_fsblk_t goal, unsigned long *count, int *errp)
{
struct buffer_head *bitmap_bh = NULL;
struct buffer_head *gdp_bh;
int group_no;
int goal_group;
ext3_grpblk_t grp_target_blk; /* blockgroup relative goal block */
ext3_grpblk_t grp_alloc_blk; /* blockgroup-relative allocated block*/
ext3_fsblk_t ret_block; /* filesyetem-wide allocated block */
int bgi; /* blockgroup iteration index */
int fatal = 0, err;
int performed_allocation = 0;
ext3_grpblk_t free_blocks; /* number of free blocks in a group */
struct super_block *sb;
struct ext3_group_desc *gdp;
struct ext3_super_block *es;
struct ext3_sb_info *sbi;
struct ext3_reserve_window_node *my_rsv = NULL;
struct ext3_block_alloc_info *block_i;
unsigned short windowsz = 0;
#ifdef EXT3FS_DEBUG
static int goal_hits, goal_attempts;
#endif
unsigned long ngroups;
unsigned long num = *count;
*errp = -ENOSPC;
sb = inode->i_sb;
if (!sb) {
printk("ext3_new_block: nonexistent device");
return 0;
}
/*
* Check quota for allocation of this block.
*/
if (vfs_dq_alloc_block(inode, num)) {
*errp = -EDQUOT;
return 0;
}
sbi = EXT3_SB(sb);
es = EXT3_SB(sb)->s_es;
ext3_debug("goal=%lu.\n", goal);
/*
* Allocate a block from reservation only when
* filesystem is mounted with reservation(default,-o reservation), and
* it's a regular file, and
* the desired window size is greater than 0 (One could use ioctl
* command EXT3_IOC_SETRSVSZ to set the window size to 0 to turn off
* reservation on that particular file)
*/
block_i = EXT3_I(inode)->i_block_alloc_info;
if (block_i && ((windowsz = block_i->rsv_window_node.rsv_goal_size) > 0))
my_rsv = &block_i->rsv_window_node;
if (!ext3_has_free_blocks(sbi)) {
*errp = -ENOSPC;
goto out;
}
/*
* First, test whether the goal block is free.
*/
if (goal < le32_to_cpu(es->s_first_data_block) ||
goal >= le32_to_cpu(es->s_blocks_count))
goal = le32_to_cpu(es->s_first_data_block);
group_no = (goal - le32_to_cpu(es->s_first_data_block)) /
EXT3_BLOCKS_PER_GROUP(sb);
goal_group = group_no;
retry_alloc:
gdp = ext3_get_group_desc(sb, group_no, &gdp_bh);
if (!gdp)
goto io_error;
free_blocks = le16_to_cpu(gdp->bg_free_blocks_count);
/*
* if there is not enough free blocks to make a new resevation
* turn off reservation for this allocation
*/
if (my_rsv && (free_blocks < windowsz)
&& (free_blocks > 0)
&& (rsv_is_empty(&my_rsv->rsv_window)))
my_rsv = NULL;
if (free_blocks > 0) {
grp_target_blk = ((goal - le32_to_cpu(es->s_first_data_block)) %
EXT3_BLOCKS_PER_GROUP(sb));
bitmap_bh = read_block_bitmap(sb, group_no);
if (!bitmap_bh)
goto io_error;
grp_alloc_blk = ext3_try_to_allocate_with_rsv(sb, handle,
group_no, bitmap_bh, grp_target_blk,
my_rsv, &num, &fatal);
if (fatal)
goto out;
if (grp_alloc_blk >= 0)
goto allocated;
}
ngroups = EXT3_SB(sb)->s_groups_count;
smp_rmb();
/*
* Now search the rest of the groups. We assume that
* group_no and gdp correctly point to the last group visited.
*/
for (bgi = 0; bgi < ngroups; bgi++) {
group_no++;
if (group_no >= ngroups)
group_no = 0;
gdp = ext3_get_group_desc(sb, group_no, &gdp_bh);
if (!gdp)
goto io_error;
/* fvdw*/
if (!gdp->bg_free_blocks_count)
continue;
/* fvdw */
free_blocks = le16_to_cpu(gdp->bg_free_blocks_count);
/*
* skip this group if the number of
* free blocks is less than half of the reservation
* window size.
*/
if (my_rsv && (free_blocks <= (windowsz/2)))
continue;
brelse(bitmap_bh);
bitmap_bh = read_block_bitmap(sb, group_no);
if (!bitmap_bh)
goto io_error;
/*
* try to allocate block(s) from this group, without a goal(-1).
*/
grp_alloc_blk = ext3_try_to_allocate_with_rsv(sb, handle,
group_no, bitmap_bh, -1, my_rsv,
&num, &fatal);
if (fatal)
goto out;
if (grp_alloc_blk >= 0)
goto allocated;
}
/*
* We may end up a bogus ealier ENOSPC error due to
* filesystem is "full" of reservations, but
* there maybe indeed free blocks avaliable on disk
* In this case, we just forget about the reservations
* just do block allocation as without reservations.
*/
if (my_rsv) {
my_rsv = NULL;
windowsz = 0;
group_no = goal_group;
goto retry_alloc;
}
/* No space left on the device */
*errp = -ENOSPC;
goto out;
allocated:
ext3_debug("using block group %d(%d)\n",
group_no, gdp->bg_free_blocks_count);
BUFFER_TRACE(gdp_bh, "get_write_access");
fatal = ext3_journal_get_write_access(handle, gdp_bh);
if (fatal)
goto out;
ret_block = grp_alloc_blk + ext3_group_first_block_no(sb, group_no);
if (in_range(le32_to_cpu(gdp->bg_block_bitmap), ret_block, num) ||
in_range(le32_to_cpu(gdp->bg_inode_bitmap), ret_block, num) ||
in_range(ret_block, le32_to_cpu(gdp->bg_inode_table),
EXT3_SB(sb)->s_itb_per_group) ||
in_range(ret_block + num - 1, le32_to_cpu(gdp->bg_inode_table),
EXT3_SB(sb)->s_itb_per_group)) {
ext3_error(sb, "ext3_new_block",
"Allocating block in system zone - "
"blocks from "E3FSBLK", length %lu",
ret_block, num);
/*
* claim_block() marked the blocks we allocated as in use. So we
* may want to selectively mark some of the blocks as free.
*/
goto retry_alloc;
}