/* ************************************************************************************ * mem_alloc.c * @description : This program will consume memory using sbrk() to a size where * there is about COMMITED_AS KB left in free{swap+ram}. * The program realized that a process can consume so much memory, * space, so it will fork more child to consume as much as memory * possible, aiming for final free{swap+ram} < COMMITED_AS. * EXEPTION: If overcommit_momory is set, the program will only * consume as much as momory as oom-killer allows, and * will exit when then limit reached even the * free{swap+ram} not < COMMITTED_AS KB. * @author : Sarunya Jimenez (sjimene@us.ibm.com) * ********************************************************************************** */ /* * Copyright (C) 2003-2006 IBM * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/sysinfo.h> #include <sys/mman.h> #include <errno.h> /////////////////////////// GLOBAL STATIC VAIRABLE FOR SIGNAL HANDLER ///////////////// static volatile sig_atomic_t sigflag; // set nonzero by sig handler static sigset_t newmask, oldmask, zeromask; //////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////// GLOBAL DEFINES //////////////////////////////////////// #define KB_VALUE 1024 // value in bytes -> 1024 bytes #define COMMITTED_AS 102400 // value in KB -> 102400 KB -> 100MB #define MALLOC_SIZE 0x10000 // = 64KB... for each sbrk(MALLOC_SIZE) in // malloc_data() // MUST ALWAYS BE POSSITIVE VALUE #define PAGE_SIZE 0x400 // = 1024 KB ///////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////// GLOBAL VARIABLES //////////////////////////////////////// long sbrk_num; // global sbrk_num to keep track of # of times sbrk() get called char *start_addr; // heap @before a process allocate memory - get updated in eat_mem() char *end_addr; // heap @after a process allocate memory - get updated in alloc_data() // and dealloc_data() ////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////// ERROR HANDLING PRINT FUNCTIONS ////////////////////////// /* ======================================================================================== * Print linux error message, will exit the current process. * ======================================================================================== */ void unix_error(char *msg) { printf("LINUX ERROR: %s: %s\n", msg, strerror(errno)); exit(0); } /* ======================================================================================== * Print functionality-error message for user process, will not exit the current process. * ======================================================================================== */ void user_error(char *msg) { printf("APPLICATION ERROR: %s\n", msg); } ///////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////// SIGNAL HANDLING FUNCTIONS /////////////////////////////////////// /* ===================================================================================== * One Signal Handler for SIGUSR1 and SIGUSR2. * ===================================================================================== */ static void sig_usr(int signo) // signal hanlder for SIGUSR1 and SIGUSR2 { sigflag = 1; } /* ======================================================================================== * SET UP signal handler before TELL_PARENT(), WAIT_PARENT(), TELL_CHILD(), WAIT_CHILD(). * - This function must be called before fork() and TELL/WAIT_PARENT/CHILD() functions. * ======================================================================================== */ void TELL_WAIT(void) { if (signal(SIGUSR1, sig_usr) == SIG_ERR) unix_error("signal (SIGUSR1) FAILED"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) unix_error("signal (SIGUSR2) FAILED"); sigemptyset(&zeromask); sigemptyset(&newmask); sigaddset(&newmask, SIGUSR1); sigaddset(&newmask, SIGUSR2); if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) unix_error("signal (SIG_BLOCK) FAILED"); } /* ======================================================================================== * TELL parent that we are done: used in child process. * - This function must be called after TELL_WAIT() setup the SIGUSR1 & SIGUSR2 propery. * - INPUT: parent process ID; can be obtained through getppid(). * ======================================================================================== */ void TELL_PARENT(pid_t pid) { kill(pid, SIGUSR2); // send signal SIGUSR2 to pid process } /* ======================================================================================== * TELL child that we are done: used in parent process. * - This function must be called after TELL_WAIT() setup the SIGUSR1 & SIGUSR2 propery. * - INPUT: child process ID; can be obtained through pid = fork() where pid > 0. * ======================================================================================== */ void TELL_CHILD(pid_t pid) { kill(pid, SIGUSR1); // send signal SIGUSR1 to pid process } /* ======================================================================================== * WAIT for parent: used in child process. * - This function must be called after TELL_WAIT() setup the SIGUSR1 & SIGUSR2 propery. * ======================================================================================== */ void WAIT_PARENT(void) { while (sigflag == 0) sigsuspend(&zeromask); // wait for child sigflag = 0; // reset signal mask to original value if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) unix_error("signal (SIG_SETMASK) FAILED"); } /* ======================================================================================== * WAIT for child: used in parent process. * - This function must be called after TELL_WAIT() setup the SIGUSR1 & SIGUSR2 propery. * ======================================================================================== */ void WAIT_CHILD(void) { while (sigflag == 0) sigsuspend(&zeromask); // wait for parent sigflag = 0; // reset signal mask to original value if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) unix_error("signal (SIG_SETMASK) FAILED"); } ///////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// MEMORY ALLOCATION FUNCTIONS ///////////////////////// /* ===================================================================================== * SET sbrk_num @start of each process to count # of sbrk() calls within that process. * - INPUT: input number for globak sbrk_num to be set to. * ===================================================================================== */ void set_sbrk_num(int in) { sbrk_num = in; } /* ======================================================================================== * PRINT system information; e.g. free {ram, swap}, total {ram, swap}. * ======================================================================================== */ void print_sysinfo(void) { struct sysinfo si; sysinfo(&si); printf ("freeram (%luKB), freeswap (%luKB), totalram (%luKB), totalswap (%luKB)\n", (si.freeram / KB_VALUE) * si.mem_unit, (si.freeswap / KB_VALUE) * si.mem_unit, (si.totalram / KB_VALUE) * si.mem_unit, (si.totalswap / KB_VALUE) * si.mem_unit); } /* ======================================================================================== * CALCULATE freeswap space. * - OUTPUT: Return size of free swap space in KB. * ======================================================================================== */ long unsigned freeswap(void) { struct sysinfo si; sysinfo(&si); return ((si.freeswap / KB_VALUE) * si.mem_unit); } /* ======================================================================================== * CALCULATE freeram space. * - OUTPUT: Return size of free ram space in KB. * ======================================================================================== */ long unsigned freeram(void) { struct sysinfo si; sysinfo(&si); return ((si.freeram / KB_VALUE) * si.mem_unit); } /* ======================================================================================== * ALLOCATE data using sbrk(incr). * - Global sbrk_num will be updated for each time calling sbrk() to increase heap size. * - OUTPUT: Return 1 if success, * 0 if failed, and will decrement the heap space for future library calls * ======================================================================================== */ int malloc_data(void) { int return_value = 0; // default return = true; intptr_t incr = MALLOC_SIZE; // 64KB char *src = NULL; // to hold addr return from sbrk(incr) long i; // loop counter src = sbrk(incr); if (((void *)src == (void *)-1) && (errno == ENOMEM)) { // error handling src = sbrk(-(2 * incr)); // freeing some space for later library calls sbrk_num -= 2; end_addr = src + (-(2 * incr)); // update end of heap } else { // sucess case // must write to data, write once for each 1KB for (i = 0x0; i < incr; i += PAGE_SIZE) src[i] = '*'; ++sbrk_num; // update global sbrk() call counter when success return_value = 1; // update return value to true end_addr = src + incr; // update end of heap } return return_value; } /* ======================================================================================== * DEALLOCATE data using sbrk(-incr). * - Global sbrk_num will be updated for each time calling sbrk() to decrease heap size. * - OUTPUT: Return 1 if success, * 0 if failed. * ======================================================================================== */ int dealloc_data(void) { int return_value = 0; // default return = true intptr_t incr = MALLOC_SIZE; // 64KB char *src = NULL; // to hold adrr return from sbrk(incr) long i; // loop counter long old_sbrk_num = sbrk_num; // save old sbrk_num counter, because sbrk_num will be updated for (i = 0; i < old_sbrk_num; ++i) { src = sbrk(-incr); // error handling: Fatal Fail if (((void *)src == (void *)-1) && (errno == ENOMEM)) goto OUT; // error --sbrk_num; // update # of sbrk() call end_addr = src + (-incr); // update end of heap } return_value = 1; // update return value to true OUT: return return_value; } /* ======================================================================================== * Write to the memory because of Copy-On-Write behavior from LINUX kernel. * IDEA: Because fork() is implemented through Copy-On-Write. This technique * delay/prevent the copy of data, child & parent share memory, and their * duplication of the address of child & parent are shared read-only. For parent * & child to have its very own separate space, both must write to their own data. * So this function will deal with the write for the child process created * by fork(). * OUTPUT: Return 1 if success, * 0 if failed. * ======================================================================================== */ int handle_COW(void) { int return_value = 0; // default return = true intptr_t incr = MALLOC_SIZE; // 64KB char *src = NULL; // to hold adrr return from sbrk(incr) char *i; // loop counter // error handling: Make sure the start_addr is not NULL if (start_addr == NULL) { user_error("start_addr from parent is not initialized"); goto OUT; } // error handling: Make sure the end_addr is not NULL if (end_addr == NULL) { user_error("end_addr from parent is not initialized"); goto OUT; } // Writing to heap if (start_addr < end_addr) { // Heap grows up to higher address for (i = start_addr; i < end_addr; i += PAGE_SIZE) { if ((freeswap() + freeram()) < COMMITTED_AS) goto OUT; *i = 'u'; } return_value = 1; } else if (start_addr > end_addr) { // Heap grows down to lower address for (i = end_addr; i > start_addr; i -= PAGE_SIZE) { if ((freeswap() + freeram()) < COMMITTED_AS) goto OUT; *i = 'd'; } return_value = 1; } else; // Heap doesn't grows OUT: return return_value; } /* ======================================================================================== * EAT lots and lots of memory... * - If a process can eat all of the free resouces * specified, that process will exit the program. * ======================================================================================== */ void eat_mem(void) { // saving the current heap pointer befoer start to allocate more memory start_addr = NULL; end_addr = NULL; start_addr = sbrk(0); // eating memory while ((freeswap() + freeram()) > COMMITTED_AS) { if (!malloc_data()) return; } print_sysinfo(); exit(0); } /* ======================================================================================== * EAT lots and lots of memory...If a process can eat all of the free resouces * specified, that process will exit the program * ======================================================================================== */ void eat_mem_no_exit(void) { // saving the current heap pointer befoer start to allocate more memory start_addr = NULL; end_addr = NULL; start_addr = sbrk(0); // eating memory while ((freeswap() + freeram()) > COMMITTED_AS) { if (!malloc_data()) break; } } /////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////// MAIN PROGRAM //////////////////////////////////////////////////// int main(int argc, char **argv) { pid_t pid; // used for fork() print_sysinfo(); // sytem resouces before start allocation set_sbrk_num(0); // at start of process, ensure sbrk_num is set eat_mem(); // @beyound this point -> 1 process can't consume all memory so it must fork a child to consume more // memory START: pid = fork(); pid = pid < 0 ? -1 : pid; switch (pid) { case -1: if (!dealloc_data()) unix_error ("SBRK(-incr) FROM DEALLOC_DATA() FAILED. FATAL!!!"); goto LAST_CONDITION; case 0: if (!handle_COW()) { // Re-touch child pages print_sysinfo(); // FINAL RESULT, LAST RESOURCES exit(0); // child can't allocate no more, DONE!!! } goto START; default: if (waitpid(-1, NULL, 0) != pid) // Parent Waiting unix_error("WAIT_PID FAILED. FATAL!!!"); exit(0); } LAST_CONDITION: TELL_WAIT(); // set up parent/child signal handler pid = fork(); pid = pid < 0 ? -1 : pid; switch (pid) { case -1: unix_error("FORK FAILED."); case 0: eat_mem_no_exit(); WAIT_PARENT(); print_sysinfo(); // FINAL RESULT, LAST RESOUCES TELL_PARENT(getppid()); exit(0); default: eat_mem_no_exit(); TELL_CHILD(pid); WAIT_CHILD(); exit(0); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////