[BUG][2.4.18+] kswapd assumes swapspace exists

From: Ross Biro (rossb@google.com)
Date: Mon Mar 10 2003 - 12:22:05 EST


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


/*
 * 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);
         
  }
  
  
}

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Sat Mar 15 2003 - 22:00:22 EST