On Mon, 17 Nov 2008 15:34:13 -0500 (EST) Steven Rostedt <[EMAIL PROTECTED]> wrote:
> > I've been hitting stack overflows on a PPC64 box, so I ran the ftrace > stack_tracer and part of the problem with that box is that it can nest > interrupts too deep. But what also worries me is that there's some heavy > hitters of stacks in generic code. Namely the fs directory has some. > > Here's the full dump of the stack (PPC64): > > ... > > do_mpage_readpage. They each use 1280 bytes of stack! Looking at the start > of these two: > > int block_read_full_page(struct page *page, get_block_t *get_block) > { > struct inode *inode = page->mapping->host; > sector_t iblock, lblock; > struct buffer_head *bh, *head, *arr[MAX_BUF_PER_PAGE]; > unsigned int blocksize; > int nr, i; > int fully_mapped = 1; > [...] > > static struct bio * > do_mpage_readpage(struct bio *bio, struct page *page, unsigned nr_pages, > sector_t *last_block_in_bio, struct buffer_head *map_bh, > unsigned long *first_logical_block, get_block_t get_block) > { > struct inode *inode = page->mapping->host; > const unsigned blkbits = inode->i_blkbits; > const unsigned blocks_per_page = PAGE_CACHE_SIZE >> blkbits; > const unsigned blocksize = 1 << blkbits; > sector_t block_in_file; > sector_t last_block; > sector_t last_block_in_file; > sector_t blocks[MAX_BUF_PER_PAGE]; > unsigned page_block; > unsigned first_hole = blocks_per_page; > struct block_device *bdev = NULL; > int length; > int fully_mapped = 1; > unsigned nblocks; > unsigned relative_block; > > > The thing that hits my eye on both is the MAX_BUF_PER_PAGE usage. That is > defined as: > > define MAX_BUF_PER_PAGE (PAGE_CACHE_SIZE / 512) > > Where PAGE_CACHE_SIZE is the same as PAGE_SIZE. > > On PPC64 I'm told that the page size is 64K, which makes the above equal > to: 64K / 512 = 128 multiply that by 8 byte words, we have 1024 bytes. > > The problem with PPC64 is that the stack size is not equal to the page > size. The stack size is only 16K not 64K. > > The above stack trace was taken right after boot up and it was already at > 14K, not too far from the 16k limit. > > Note, I was using a default config that had CONFIG_IRQSTACKS off and > CONFIG_PPC_64K_PAGES on. > Far be it from me to apportion blame, but THIS IS ALL LINUS'S FAULT!!!!! :) I fixed this six years ago. See http://lkml.org/lkml/2002/6/17/68 I still have the patch but for some reason it appears to get some rejects. However I think the approach (whatever it was ;)) is still usable. Perhaps some keen young thing has time to get down and redo it. This patch fixes some potential stack consumption problems. The storage for sector_t blocks[MAX_BUF_PER_PAGE]; and struct buffer_head *arr[MAX_BUF_PER_PAGE]; will consume a kilobyte with 64k page, 64-bit sector_t. And on the path do_mpage_readpage ->block_read_full_page -> <page allocation> ->mpage_writepage they can be nested three-deep. 3k of stack gone. Presumably in this case the stack page would be 64k anyway, so that's not a problem. However if PAGE_SIZE=4k and PAGE_CACHE_SIZE=64k, we die. I've yet to see any reason for larger PAGE_CACHE_SIZE, but this is a neater implementation anyway. The patch removes MAX_BUF_PER_PAGE and instead walks the page's buffer ring to work out which buffers need to be submitted. --- 2.5.22/fs/mpage.c~stack-space Sun Jun 16 22:50:17 2002 +++ 2.5.22-akpm/fs/mpage.c Sun Jun 16 22:50:17 2002 @@ -169,8 +169,9 @@ do_mpage_readpage(struct bio *bio, struc const unsigned blocksize = 1 << blkbits; struct bio_vec *bvec; sector_t block_in_file; - sector_t last_block; - sector_t blocks[MAX_BUF_PER_PAGE]; + sector_t last_file_block; + sector_t first_page_block = -1; + sector_t last_page_block = -1; unsigned page_block; unsigned first_hole = blocks_per_page; struct block_device *bdev = NULL; @@ -180,12 +181,12 @@ do_mpage_readpage(struct bio *bio, struc goto confused; block_in_file = page->index << (PAGE_CACHE_SHIFT - blkbits); - last_block = (inode->i_size + blocksize - 1) >> blkbits; + last_file_block = (inode->i_size + blocksize - 1) >> blkbits; for (page_block = 0; page_block < blocks_per_page; page_block++, block_in_file++) { bh.b_state = 0; - if (block_in_file < last_block) { + if (block_in_file < last_file_block) { if (get_block(inode, block_in_file, &bh, 0)) goto confused; } @@ -199,10 +200,14 @@ do_mpage_readpage(struct bio *bio, struc if (first_hole != blocks_per_page) goto confused; /* hole -> non-hole */ - /* Contiguous blocks? */ - if (page_block && blocks[page_block-1] != bh.b_blocknr-1) - goto confused; - blocks[page_block] = bh.b_blocknr; + if (page_block) { + /* Contiguous blocks? */ + if (bh.b_blocknr != last_page_block + 1) + goto confused; + } else { + first_page_block = bh.b_blocknr; + } + last_page_block = bh.b_blocknr; bdev = bh.b_bdev; } @@ -222,7 +227,7 @@ do_mpage_readpage(struct bio *bio, struc * This page will go to BIO. Do we need to send this BIO off first? */ if (bio && (bio->bi_idx == bio->bi_vcnt || - *last_block_in_bio != blocks[0] - 1)) + *last_block_in_bio != first_page_block - 1)) bio = mpage_bio_submit(READ, bio); if (bio == NULL) { @@ -230,7 +235,7 @@ do_mpage_readpage(struct bio *bio, struc if (nr_bvecs > nr_pages) nr_bvecs = nr_pages; - bio = mpage_alloc(bdev, blocks[0] << (blkbits - 9), + bio = mpage_alloc(bdev, first_page_block << (blkbits - 9), nr_bvecs, GFP_KERNEL); if (bio == NULL) goto confused; @@ -244,7 +249,7 @@ do_mpage_readpage(struct bio *bio, struc if (buffer_boundary(&bh) || (first_hole != blocks_per_page)) bio = mpage_bio_submit(READ, bio); else - *last_block_in_bio = blocks[blocks_per_page - 1]; + *last_block_in_bio = last_page_block; out: return bio; @@ -322,9 +327,10 @@ mpage_writepage(struct bio *bio, struct unsigned long end_index; const unsigned blocks_per_page = PAGE_CACHE_SIZE >> blkbits; struct bio_vec *bvec; - sector_t last_block; + sector_t last_file_block; sector_t block_in_file; - sector_t blocks[MAX_BUF_PER_PAGE]; + sector_t first_page_block = -1; + sector_t last_page_block = -1; unsigned page_block; unsigned first_unmapped = blocks_per_page; struct block_device *bdev = NULL; @@ -355,11 +361,13 @@ mpage_writepage(struct bio *bio, struct if (!buffer_dirty(bh) || !buffer_uptodate(bh)) goto confused; - if (page_block) { - if (bh->b_blocknr != blocks[page_block-1] + 1) + if (page_block++) { + if (bh->b_blocknr != last_page_block + 1) goto confused; + } else { + first_page_block = bh->b_blocknr; } - blocks[page_block++] = bh->b_blocknr; + last_page_block = bh->b_blocknr; boundary = buffer_boundary(bh); bdev = bh->b_bdev; } while ((bh = bh->b_this_page) != head); @@ -381,7 +389,7 @@ mpage_writepage(struct bio *bio, struct */ BUG_ON(!PageUptodate(page)); block_in_file = page->index << (PAGE_CACHE_SHIFT - blkbits); - last_block = (inode->i_size - 1) >> blkbits; + last_file_block = (inode->i_size - 1) >> blkbits; for (page_block = 0; page_block < blocks_per_page; ) { struct buffer_head map_bh; @@ -392,13 +400,16 @@ mpage_writepage(struct bio *bio, struct unmap_underlying_metadata(map_bh.b_bdev, map_bh.b_blocknr); if (page_block) { - if (map_bh.b_blocknr != blocks[page_block-1] + 1) + if (map_bh.b_blocknr != last_page_block + 1) goto confused; + } else { + first_page_block = map_bh.b_blocknr; } - blocks[page_block++] = map_bh.b_blocknr; + page_block++; + last_page_block = map_bh.b_blocknr; boundary = buffer_boundary(&map_bh); bdev = map_bh.b_bdev; - if (block_in_file == last_block) + if (block_in_file == last_file_block) break; block_in_file++; } @@ -424,13 +435,13 @@ page_is_mapped: * This page will go to BIO. Do we need to send this BIO off first? */ if (bio && (bio->bi_idx == bio->bi_vcnt || - *last_block_in_bio != blocks[0] - 1)) + *last_block_in_bio != first_page_block - 1)) bio = mpage_bio_submit(WRITE, bio); if (bio == NULL) { unsigned nr_bvecs = MPAGE_BIO_MAX_SIZE / PAGE_CACHE_SIZE; - bio = mpage_alloc(bdev, blocks[0] << (blkbits - 9), + bio = mpage_alloc(bdev, first_page_block << (blkbits - 9), nr_bvecs, GFP_NOFS); if (bio == NULL) goto confused; @@ -464,7 +475,7 @@ page_is_mapped: if (boundary || (first_unmapped != blocks_per_page)) bio = mpage_bio_submit(WRITE, bio); else - *last_block_in_bio = blocks[blocks_per_page - 1]; + *last_block_in_bio = last_page_block; goto out; confused: --- 2.5.22/fs/buffer.c~stack-space Sun Jun 16 22:50:17 2002 +++ 2.5.22-akpm/fs/buffer.c Sun Jun 16 23:22:48 2002 @@ -1799,7 +1799,7 @@ int block_read_full_page(struct page *pa { struct inode *inode = page->mapping->host; unsigned long iblock, lblock; - struct buffer_head *bh, *head, *arr[MAX_BUF_PER_PAGE]; + struct buffer_head *bh, *head; unsigned int blocksize, blocks; int nr, i; @@ -1842,7 +1842,7 @@ int block_read_full_page(struct page *pa if (buffer_uptodate(bh)) continue; } - arr[nr++] = bh; + nr++; } while (i++, iblock++, (bh = bh->b_this_page) != head); if (!nr) { @@ -1857,24 +1857,26 @@ int block_read_full_page(struct page *pa } /* Stage two: lock the buffers */ - for (i = 0; i < nr; i++) { - bh = arr[i]; - lock_buffer(bh); - mark_buffer_async_read(bh); - } + do { + if (!buffer_uptodate(bh)) { + lock_buffer(bh); + mark_buffer_async_read(bh); + } + } while ((bh = bh->b_this_page) != head); /* * Stage 3: start the IO. Check for uptodateness * inside the buffer lock in case another process reading * the underlying blockdev brought it uptodate (the sct fix). */ - for (i = 0; i < nr; i++) { - bh = arr[i]; - if (buffer_uptodate(bh)) - end_buffer_async_read(bh, 1); - else - submit_bh(READ, bh); - } + do { + if (buffer_async_read(bh)) { + if (buffer_uptodate(bh)) + end_buffer_async_read(bh, 1); + else + submit_bh(READ, bh); + } + } while ((bh = bh->b_this_page) != head); return 0; } @@ -2490,8 +2492,8 @@ static void bh_mempool_free(void *elemen return kmem_cache_free(bh_cachep, element); } -#define NR_RESERVED (10*MAX_BUF_PER_PAGE) -#define MAX_UNUSED_BUFFERS NR_RESERVED+20 + +#define MEMPOOL_BUFFERS (32 * PAGE_CACHE_SIZE / 512) void __init buffer_init(void) { @@ -2500,7 +2502,7 @@ void __init buffer_init(void) bh_cachep = kmem_cache_create("buffer_head", sizeof(struct buffer_head), 0, SLAB_HWCACHE_ALIGN, init_buffer_head, NULL); - bh_mempool = mempool_create(MAX_UNUSED_BUFFERS, bh_mempool_alloc, + bh_mempool = mempool_create(MEMPOOL_BUFFERS, bh_mempool_alloc, bh_mempool_free, NULL); for (i = 0; i < ARRAY_SIZE(bh_wait_queue_heads); i++) init_waitqueue_head(&bh_wait_queue_heads[i].wqh); --- 2.5.22/fs/block_dev.c~stack-space Sun Jun 16 22:50:17 2002 +++ 2.5.22-akpm/fs/block_dev.c Sun Jun 16 22:50:17 2002 @@ -23,8 +23,6 @@ #include <asm/uaccess.h> -#define MAX_BUF_PER_PAGE (PAGE_CACHE_SIZE / 512) - static unsigned long max_block(struct block_device *bdev) { unsigned int retval = ~0U; --- 2.5.22/include/linux/buffer_head.h~stack-space Sun Jun 16 22:50:17 2002 +++ 2.5.22-akpm/include/linux/buffer_head.h Sun Jun 16 23:22:47 2002 @@ -29,8 +29,6 @@ enum bh_state_bits { */ }; -#define MAX_BUF_PER_PAGE (PAGE_CACHE_SIZE / 512) - struct page; struct kiobuf; struct buffer_head; --- 2.5.22/fs/ntfs/aops.c~stack-space Sun Jun 16 22:50:17 2002 +++ 2.5.22-akpm/fs/ntfs/aops.c Sun Jun 16 23:22:46 2002 @@ -104,7 +104,7 @@ static int ntfs_file_read_block(struct p LCN lcn; ntfs_inode *ni; ntfs_volume *vol; - struct buffer_head *bh, *head, *arr[MAX_BUF_PER_PAGE]; + struct buffer_head *bh, *head; sector_t iblock, lblock, zblock; unsigned int blocksize, blocks, vcn_ofs; int i, nr; @@ -116,11 +116,12 @@ static int ntfs_file_read_block(struct p blocksize_bits = VFS_I(ni)->i_blkbits; blocksize = 1 << blocksize_bits; - if (!page_has_buffers(page)) + if (!page_has_buffers(page)) { create_empty_buffers(page, blocksize, 0); + if (!page_has_buffers(page)) /* This can't happen */ + return -ENOMEM; + } bh = head = page_buffers(page); - if (!bh) - return -ENOMEM; blocks = PAGE_CACHE_SIZE >> blocksize_bits; iblock = page->index << (PAGE_CACHE_SHIFT - blocksize_bits); @@ -138,10 +139,12 @@ static int ntfs_file_read_block(struct p /* Loop through all the buffers in the page. */ nr = i = 0; do { + BUG_ON(buffer_async_read(bh)); if (unlikely(buffer_uptodate(bh))) continue; if (unlikely(buffer_mapped(bh))) { - arr[nr++] = bh; + set_buffer_async_read(bh); + nr++; continue; } bh->b_bdev = vol->sb->s_bdev; @@ -167,7 +170,8 @@ retry_remap: set_buffer_mapped(bh); /* Only read initialized data blocks. */ if (iblock < zblock) { - arr[nr++] = bh; + set_buffer_async_read(bh); + nr++; continue; } /* Fully non-initialized data block, zero it. */ @@ -208,15 +212,18 @@ handle_zblock: /* Check we have at least one buffer ready for i/o. */ if (nr) { /* Lock the buffers. */ - for (i = 0; i < nr; i++) { - struct buffer_head *tbh = arr[i]; - lock_buffer(tbh); - tbh->b_end_io = end_buffer_read_file_async; - set_buffer_async_read(tbh); - } + do { + if (buffer_async_read(bh)) { + lock_buffer(bh); + bh->b_end_io = end_buffer_read_file_async; + } + } while ((bh = bh->b_this_page) != head); + /* Finally, start i/o on the buffers. */ - for (i = 0; i < nr; i++) - submit_bh(READ, arr[i]); + do { + if (buffer_async_read(bh)) + submit_bh(READ, bh); + } while ((bh = bh->b_this_page) != head); return 0; } /* No i/o was scheduled on any of the buffers. */ @@ -404,7 +411,7 @@ static int ntfs_mftbmp_readpage(ntfs_vol { VCN vcn; LCN lcn; - struct buffer_head *bh, *head, *arr[MAX_BUF_PER_PAGE]; + struct buffer_head *bh, *head; sector_t iblock, lblock, zblock; unsigned int blocksize, blocks, vcn_ofs; int nr, i; @@ -416,11 +423,12 @@ static int ntfs_mftbmp_readpage(ntfs_vol blocksize = vol->sb->s_blocksize; blocksize_bits = vol->sb->s_blocksize_bits; - if (!page_has_buffers(page)) + if (!page_has_buffers(page)) { create_empty_buffers(page, blocksize, 0); + if (!page_has_buffers(page)) /* This can't happen */ + return -ENOMEM; + } bh = head = page_buffers(page); - if (!bh) - return -ENOMEM; blocks = PAGE_CACHE_SIZE >> blocksize_bits; iblock = page->index << (PAGE_CACHE_SHIFT - blocksize_bits); @@ -431,10 +439,12 @@ static int ntfs_mftbmp_readpage(ntfs_vol /* Loop through all the buffers in the page. */ nr = i = 0; do { + BUG_ON(buffer_async_read(bh)); if (unlikely(buffer_uptodate(bh))) continue; if (unlikely(buffer_mapped(bh))) { - arr[nr++] = bh; + set_buffer_async_read(bh); + nr++; continue; } bh->b_bdev = vol->sb->s_bdev; @@ -457,7 +467,8 @@ static int ntfs_mftbmp_readpage(ntfs_vol set_buffer_mapped(bh); /* Only read initialized data blocks. */ if (iblock < zblock) { - arr[nr++] = bh; + set_buffer_async_read(bh); + nr++; continue; } /* Fully non-initialized data block, zero it. */ @@ -491,15 +502,18 @@ handle_zblock: /* Check we have at least one buffer ready for i/o. */ if (nr) { /* Lock the buffers. */ - for (i = 0; i < nr; i++) { - struct buffer_head *tbh = arr[i]; - lock_buffer(tbh); - tbh->b_end_io = end_buffer_read_mftbmp_async; - set_buffer_async_read(tbh); - } + do { + if (buffer_async_read(bh)) { + lock_buffer(bh); + bh->b_end_io = end_buffer_read_mftbmp_async; + } + } while ((bh = bh->b_this_page) != head); + /* Finally, start i/o on the buffers. */ - for (i = 0; i < nr; i++) - submit_bh(READ, arr[i]); + do { + if (buffer_async_read(bh)) + submit_bh(READ, bh); + } while ((bh = bh->b_this_page) != head); return 0; } /* No i/o was scheduled on any of the buffers. */ @@ -643,7 +657,7 @@ int ntfs_mst_readpage(struct file *dir, LCN lcn; ntfs_inode *ni; ntfs_volume *vol; - struct buffer_head *bh, *head, *arr[MAX_BUF_PER_PAGE]; + struct buffer_head *bh, *head; sector_t iblock, lblock, zblock; unsigned int blocksize, blocks, vcn_ofs; int i, nr; @@ -658,11 +672,12 @@ int ntfs_mst_readpage(struct file *dir, blocksize_bits = VFS_I(ni)->i_blkbits; blocksize = 1 << blocksize_bits; - if (!page_has_buffers(page)) + if (!page_has_buffers(page)) { create_empty_buffers(page, blocksize, 0); + if (!page_has_buffers(page)) /* This can't happen */ + return -ENOMEM; + } bh = head = page_buffers(page); - if (!bh) - return -ENOMEM; blocks = PAGE_CACHE_SIZE >> blocksize_bits; iblock = page->index << (PAGE_CACHE_SHIFT - blocksize_bits); @@ -678,10 +693,12 @@ int ntfs_mst_readpage(struct file *dir, /* Loop through all the buffers in the page. */ nr = i = 0; do { + BUG_ON(buffer_async_read(bh)); if (unlikely(buffer_uptodate(bh))) continue; if (unlikely(buffer_mapped(bh))) { - arr[nr++] = bh; + set_buffer_async_read(bh); + nr++; continue; } bh->b_bdev = vol->sb->s_bdev; @@ -707,7 +724,8 @@ retry_remap: set_buffer_mapped(bh); /* Only read initialized data blocks. */ if (iblock < zblock) { - arr[nr++] = bh; + set_buffer_async_read(bh); + nr++; continue; } /* Fully non-initialized data block, zero it. */ @@ -748,15 +766,18 @@ handle_zblock: /* Check we have at least one buffer ready for i/o. */ if (nr) { /* Lock the buffers. */ - for (i = 0; i < nr; i++) { - struct buffer_head *tbh = arr[i]; - lock_buffer(tbh); - tbh->b_end_io = end_buffer_read_mst_async; - set_buffer_async_read(tbh); - } + do { + if (buffer_async_read(bh)) { + lock_buffer(bh); + bh->b_end_io = end_buffer_read_mst_async; + } + } while ((bh = bh->b_this_page) != head); + /* Finally, start i/o on the buffers. */ - for (i = 0; i < nr; i++) - submit_bh(READ, arr[i]); + do { + if (buffer_async_read(bh)) + submit_bh(READ, bh); + } while ((bh = bh->b_this_page) != head); return 0; } /* No i/o was scheduled on any of the buffers. */ - _______________________________________________ Linuxppc-dev mailing list Linuxppc-dev@ozlabs.org https://ozlabs.org/mailman/listinfo/linuxppc-dev