--=_courier-25230-1047316969-0001-2
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit
I've verified this in 2.4.21-pre5 by code inspection and can trigger the 
problem on 2.4.18.  It appears to have been fixed in 2.5.
The folowing code vmscan.c assumes that there is available swap space.
        /*
         * this is the non-racy check for busy page.
         */
        if (!page->mapping || !is_page_cache_freeable(page)) {
            spin_unlock(&pagecache_lock);
            UnlockPage(page);
page_mapped:
            if (--max_mapped >= 0)
                continue;
            /*
             * Alert! We've found too many mapped pages on the
             * inactive list, so we start swapping out now!
             */
            spin_unlock(&pagemap_lru_lock);
            swap_out(priority, gfp_mask, classzone);
            return nr_pages;
        }
If there is no swap space, then unfreeable pages are left on the 
inactive queue and the vmtree is walked rather than going through the 
rest of the inactive queue.  I believe something like
        /*
         * this is the non-racy check for busy page.
         */
        if (!page->mapping || !is_page_cache_freeable(page)) {
            spin_unlock(&pagecache_lock);
            UnlockPage(page);
page_mapped:
                        /* If we don't have any swap space left, there
                           is no reason to worry about pages that do
                           not have swap associated with them, there
                           is nothing we can do about it. */
                        if (!page->mapping && !swap_avail()) {
                                /* Let's make the page active since we
                                   cannot swap it out.  It get's it off
                                   the inactive list. */
                                spin_unlock(&pagemap_lru_lock);
                                activate_page(page);
                                ClearPageReferenced(page);
                                spin_lock(&pagemap_lru_lock);
                                continue;
                        }
            if (--max_mapped >= 0)
                continue;
            /*
             * Alert! We've found too many mapped pages on the
             * inactive list, so we start swapping out now!
             */
            spin_unlock(&pagemap_lru_lock);
            swap_out(priority, gfp_mask, classzone);
            return nr_pages;
        }
will work better when there is no swap space available.  If this change 
is made, it may also be necessary to limit refill_inactive to prevent it 
from using too much cpu.  This bug can be triggered with the attached 
code and the correct parameters.  In particular on a 3 gigabyte machine 
with no swap,
for i in $(seq 0 9); do dd if=/dev/zero of=file$i bs=1024k count=512; done
killmm 1032735283 2 9
Usually causes an out of memory error when there is hundreds of 
megabytes of cache.
    Ross
--=_courier-25230-1047316969-0001-2
Content-Type: text/plain; name="killmm.c"; charset=iso-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="killmm.c"
/*
 *  killmm.c attempts to exploit bugs in the mm to cause a crash or
 *  other undesired behaviour.
 *  Copyright (C) 2002 Google
 *  Written by Ross Biro
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <malloc.h>
#include <errno.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <asm/page.h>
/* 512 Meg */ 
#define BLOCKSIZE 512*1024*1024 
#define USEMLOCK 1
int main(int argc, char *argv[]) {
  size_t memory = atoi(argv[1]);
  int blocks = atoi(argv[2]);
  int maxfiles = atoi(argv[3]);
  unsigned char *cptr; 
  int i, j, k;
  void **ptrs = (void **)malloc(blocks * sizeof(*ptrs));
  if (ptrs == NULL) {
    fprintf (stderr, "Unable to allocate %d bytes: %s\n",
             sizeof(*ptrs) * blocks,
             strerror(errno));
    return -1;
  }
  
  /* The first thing we do is allocate a bunch of memory. */
  cptr = (unsigned char *)malloc(memory);
  if (cptr == NULL) {
    fprintf (stderr, "Unable to allocate %d bytes: %s\n", memory,
             strerror(errno));
    return -1;
  }
  /* now we want to make it all dirty. */
  for (i = 0; i < memory; i++) {
    cptr[i] = (unsigned char)(i&0xff);
    if ((i & 0xffffff) == 0) {
      printf ("Initializing memory: %d\n", i);
    }
  }
  
  /* Now we have a bunch of dirty memory.  Map in huge files. */
  for (i = 0; i < maxfiles; i++) {
    char filename[1024];
    int fd;
    int ind = i%blocks;
    if (ptrs[ind] != NULL) {
      printf ("Unmapping block %d @ %08X\n", ind, ptrs[ind]);
#ifdef USEMLOCK 
     munlock(ptrs[ind], BLOCKSIZE);
#endif
      munmap(ptrs[ind], BLOCKSIZE);
    }
    sprintf (filename, "file%d", i);
    printf ("Loading file %s into slot %d\n",
            filename, ind);
    fd = open (filename, O_RDONLY);
    if (fd < 0) {
      fprintf (stderr, "Unable to open %s: %s\n", filename, strerror(errno));
      return -1;
    }
    ptrs[ind] = mmap (NULL, BLOCKSIZE, PROT_READ, MAP_PRIVATE, fd, 0);
    if (ptrs[ind] == NULL) {
      fprintf (stderr, "Unable to map file %s: %s\n", 
               filename, strerror(errno));
      return -1;
    }
#ifdef USEMLOCK
    if (mlock(ptrs[ind], BLOCKSIZE) < 0) {
      fprintf (stderr, "Unable to lock mem for %s: %s\n",
               filename, strerror(errno));
      return -1;
    }
#else
    // Page in the memory the old fashioned way.
    for (j = 0; j <BLOCKSIZE; j+= PAGE_SIZE) {
      k += ((char *)ptrs[ind])[j];
    }
#endif
    printf ("Block %d at %08X\n", ind, ptrs[ind]);
    close(fd);
         
  }
  
  
}
--=_courier-25230-1047316969-0001-2--