Como o OSX controla as condições Out of Memory?

2

O Linux usa o killer da OOM; o que os Macs fazem?

    
por user1529891 25.01.2016 / 20:15

1 resposta

1

O Mac OS X permite que você aloque "muito", embora o sistema possa se debater freneticamente ou ficar bem, dependendo de como a memória alocada é usada por qualquer código subseqüente. Eu provavelmente tive que pkill o seguinte código depois que ele alocou "um monte" de inteiros (via -M flag), embora tenha sido um tempo desde que eu testei no Mac OS X.

/*
 # (Ab)uses memory via the allocation of a block of memory, with a given
 # number of worker threads that then read and write values randomly
 # within that space. This code was motivated by the fact that while
 # 'perl -e '1 while 1' &' can run up the CPU, one is more likely to run
 # into fork limits than CPU bottlenecks on modern multi-processor
 # systems. This code better exercises a system. Basically, it offers a
 # means to bog a system down, presumably for testing purposes, race
 # condition exploration, or to annoy the local sysadmin.
 #
 # System resource limits may need to be adjusted; see login.conf(5) on
 # OpenBSD, for example. There may also be per-process limitations. A
 # good way to bog the system down is to run something like:
 #
 #   for i in ...; do ./usemem -M -t ... & done
 #
 # Where the number of processes to launch and the thread count are
 # suitable to the number of CPUs or desired level of insanity. A timed
 # pkill(1) or easy access to reset the test system should be arranged
 # for, given that too many instances of this program may render a
 # system quite inoperative--a desktop linux system 'vmstat 1' recorded
 # 12 million context switches in an entry, presumably over some time
 # period longer than a second, given how poorly that system was
 # performing. Shortly thereafter, the display froze up, and the system
 # had to be power cycled (I could still ping the box).
 #
 # The original intent of this code was to try to trigger a race
 # condition on:
 #
 #   alarm(...);
 #   somethingthatblocksforever();
 #   alarm(0);
 #     // -- paraphrased from Stevens, APUE (1st edition), p. 286.
 #
 # whereby the system is made busy enough that the SIGALRM happens
 # before the somethingthatblocks() call can be started, but after the
 # alarm(...) is established, so that the process then blocks forever.
 # It is perhaps far more likely that the system will be rendered
 # unusable (and monitoring notice this) than to hit this unlikely edge
 # case. If a system has been inordinately busy or slow, a reboot may
 # well be in order as soon as is practical. This will eliminate the
 # possiblilty that any process remains stuck on some rare edge case
 # such as the above.
 #
 # Portability: -M does sensible things on OpenBSD; on Linux and Mac OS
 # X it runs afoul various amounts of stupidity or insanity that may
 # require '-M -m upperlimit' to set the maximum allowed allocation.
 # This will help prevent the OOM killer from blasting away or the
 # kernel optimistically trying to allocate 17,592,186,044,416 integers
 # and then somehow getting bogged down.
 #
 # The code used to try to realloc() the array upwards, if possible,
 # though that on Linux ran afoul the OOM killer. Since dead processes
 # do not much tax a system, only downsizes are done. Launch more
 # instances of this script to use up the remaining memory.
 #
 # Swapping might be induced by starting an instance, then sending it
 # the CONT signal, then starting more instances? Need to test this.
 */

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>

// https://github.com/thrig/goptfoo
#include <goptfoo.h>

// https://github.com/thrig/libjkiss
#include <jkiss.h>

#define MOSTMEMPOSSIBLE ( (ULONG_MAX < SIZE_MAX) ? ULONG_MAX : SIZE_MAX )

bool Flag_Auto_Mem;             // -M
unsigned long Flag_Memory;      // -m (amount, though internally # of ints)
unsigned long Flag_Threads;     // -t

int *Memory;

pthread_mutex_t Lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t Done_Malloc = PTHREAD_COND_INITIALIZER;
pthread_cond_t Never_Happens = PTHREAD_COND_INITIALIZER;

void emit_help(void);
void *worker(void *unused);

int main(int argc, char *argv[])
{
    int ch;
    pthread_t *tids;

    jkiss64_init(NULL);

    while ((ch = getopt(argc, argv, "h?Mm:t:")) != -1) {
        switch (ch) {

        case 'M':
            Flag_Auto_Mem = true;
            break;

        case 'm':
            Flag_Memory = flagtoul(ch, optarg, 1UL, MOSTMEMPOSSIBLE);
            break;

        case 't':
            Flag_Threads = flagtoul(ch, optarg, 1UL, ULONG_MAX);
            break;

        case 'h':
        case '?':
        default:
            emit_help();
            /* NOTREACHED */
        }
    }
    argc -= optind;
    argv += optind;

    if ((Flag_Memory == 0 && !Flag_Auto_Mem) || Flag_Threads == 0)
        emit_help();

    /* Ahh gee em. Complicated. See, if you malloc() the most memory
     * possible, then there is no space for the threads. So must spin
     * the threads up first, and then binary search realloc() to find
     * the most memory possible. Presumably other instances of this
     * program or other processes will be run to eat up any remaining
     * memory on the system.
     */

    if ((tids = calloc(sizeof(pthread_t), Flag_Threads)) == NULL)
        err(EX_OSERR, "could not calloc() threads list");

    for (unsigned long i = 0; i < Flag_Threads; i++) {
        if (pthread_create(&tids[i], NULL, worker, NULL) != 0)
            err(EX_OSERR, "could not pthread_create() thread %lu", i);
    }

    if (Flag_Auto_Mem) {
        if (!Flag_Memory)
            Flag_Memory = MOSTMEMPOSSIBLE;

        Flag_Memory /= sizeof(int);

        while (Flag_Memory) {
            if ((Memory = malloc(Flag_Memory * sizeof(int))) == NULL) {
                Flag_Memory >>= 1;
            } else {
                break;
            }
        }

        if (Memory) {
            fprintf(stderr, "info: auto-alloc picks %lu ints\n", Flag_Memory);
        } else {
            err(EX_OSERR, "could not auto-malloc() %lu ints", Flag_Memory);
        }
    } else {
        Flag_Memory /= sizeof(int);
        if ((Memory = malloc(Flag_Memory * sizeof(int))) == NULL)
            err(EX_OSERR, "could not malloc() %lu ints", Flag_Memory);
    }

    memset(Memory, 1, Flag_Memory * sizeof(int));

    pthread_cond_broadcast(&Done_Malloc);

    pthread_mutex_lock(&Lock);
    pthread_cond_wait(&Never_Happens, &Lock);
    pthread_mutex_unlock(&Lock);

    /* NOTREACHED */
    exit(1);
}

void emit_help(void)
{
    fprintf(stderr, "Usage: usemem [-M|-m memory|-M -m mem] -t threads\n");
    exit(EX_USAGE);
}

void *worker(void *unused)
{
    unsigned long idx;

    pthread_mutex_lock(&Lock);
    pthread_cond_wait(&Done_Malloc, &Lock);
    pthread_mutex_unlock(&Lock);

    for (;;) {
        // do not (much) care about modulo bias as most memory amounts will
        // be vastly less than UINT64_MAX
        idx = jkiss64_rand() % Flag_Memory;
        Memory[idx] = (idx % 2 == 1)
            ? Memory[jkiss64_rand() % Flag_Memory]
            : rand();
    }

    /* NOTREACHED */
}

O OpenBSD, ao contrário, falha no malloc se você pedir mais do que está disponível.

    
por 26.01.2016 / 00:28