Patch ID	: P95031508
Summary		: anon: reservations below zero complaints
Status		: Mandatory
Date		: 03/15/95
Release		: OS 4.1C
Architectures	: Series5 Series6 S4000
Affected bugs	:
	2741

Changed files	:
	sys/sunwindowdev/ws_dispense.c (delta 1.3)
	sys/vm/seg_vn.c (delta 1.3)

Problem Description:

Creating a block of address space by memory-mapping /dev/zero and then
releasing sections of it can, under some circumstances, cause the count
of swap space that is reserved to go negative.  This causes all sorts
of problems in the long run, when the now over-committed swap space is
exhausted.

The kernel will often complain about "anon: reservations below zero".
Sometimes, there will also be an apparently-unrelated panic after many of
these complaints.

The problem may also directly result in a panic by causing the failure
of a sanity check in the kernel.  This requires a very specific sequence
of mmap(2) and munmap(2) calls.

The attached program demonstrates the problem.  Running it without arguments
will trigger the sanity-check panic referred to above.
========================================================================
#include <sys/types.h>
#include <sys/mman.h>
#define Series5
#include <machine/param.h>	/* for PAGESIZE */
#include <fcntl.h>
#include <stdio.h>

#define LARGE	(5*PAGESIZE)
#define SMALL	(1*PAGESIZE)

#define UNMAP_SIZE	(2*SMALL)

main(argc, argv)
int argc;
char *argv[];
{
	extern int getopt();

	int fd;
	caddr_t base;
	caddr_t unmap;
	int opt;
	
	/*
	 * Pick an address that is somewhere `safe'.  This is what
	 * Emacs 19 uses, and seems a good default.  It's well away
	 * from our data area, and even farther away from where shared
	 * libraries get brought in.
	 */

	base = (caddr_t) 0x4000000;

	/*
	 * Default to unmapping in the middle of the segment.  This
	 * will trip over the explicit panic in the kernel, rather
	 * than just eventually causing "anon: reservations below
	 * zero" complaints.
	 */

	unmap = base + SMALL;

	/*
	 * There are three cases in the kernel's handling of unmapping
	 * part of a segment -- at the beginning of the segment, at
	 * the end of the segment, and in the middle of the segment.
	 * Choose one of -b, -m, -e to exercise the corresponding
	 * kernel code.
	 */

	while ((opt = getopt (argc, argv, "bme")) != -1) {
		switch (opt) {
		case 'b': {
			unmap = base;
			break;
		}
		case 'm': {
			unmap = base + SMALL;
			break;
		}
		case 'e': {
			unmap = (base + LARGE + SMALL) - UNMAP_SIZE;
			break;
		}
		default: {
			fprintf (stderr, "usage: bad_map [-s|-m|-e]\n");
			exit (1);
		}
		}
	}
	
	/*
	 * The problem only seems to appear when multiple mappings of
	 * /dev/zero are used.  This has not been investigated
	 * rigorously, however.
	 */

	fd = open ("/dev/zero", O_RDWR);
	if (fd == -1) {
		perror ("open(/dev/zero)");
		exit (1);
	}
	
	/*
	 * Create the initial mapping, larger than the combined size of
	 * all subsequent mappings.  The anonymous reservation is charged to
	 * the anonmap that is created for this segment.
	 */

	if (mmap (base, LARGE,
		  PROT_READ|PROT_WRITE,
		  MAP_SHARED|MAP_FIXED,
		  3, 0) != base) {
		perror ("could not do initial mapping at desired address");
		exit (1);
	}
	
	/*
	 * Make a small mapping, such that it will be joined with the
	 * previous mapping's segment (i.e., contiguous addresses).
	 * The reservation is charged to the segment, not the anonmap.
	 * No investigation of joining at the front rather than at the end
	 * has been made.
	 */

	if (mmap (base+LARGE, SMALL,
		  PROT_READ|PROT_WRITE,
		  MAP_SHARED|MAP_FIXED,
		  3, 0) != (base+LARGE)) {
		perror ("could not do second mapping at desired address");
		exit (1);
	}
	
	/*
	 * Try not to trash filesystem too badly -- cause
	 * dirty pages to have i/o initiated, and give them a chance
	 * to drain to disk.
	 */

	sync();
	sleep(1);
	
	/*
	 * Remove a segment of the contiguous space, larger than the
	 * second mapping.  This will cause the anonymous reservation
	 * to go negative, as the kernel assumes that since the
	 * segment has some reserved space charged to it, all of the
	 * space reserved for this segment was charged to this
	 * segment, and none to the underlying anonmap.
	 */

	if (munmap (unmap, UNMAP_SIZE) == -1) {
		perror ("munmap failed");
		exit (1);
	}
	
	/*
	 * Getting this far means that the bug has been fixed.
	 */

	printf ("Mappings and unmappings succeeded\n");
	exit (0);
}
