LCOV - code coverage report
Current view: top level - test - oom.c (source / functions) Hit Total Coverage
Test: mkernel.info Lines: 116 123 94.3 %
Date: 2024-07-29 18:52:44 Functions: 12 12 100.0 %

          Line data    Source code
       1             : /**   @file  oom.c
       2             :  *   @brief
       3             :  *  @author  François Cerbelle (Fanfan), francois@cerbelle.net
       4             :  *
       5             :  *  @internal
       6             :  *       Created:  21/06/2024
       7             :  *      Revision:  none
       8             :  * Last modified:  2024-07-26 00:57
       9             :  *      Compiler:  gcc
      10             :  *  Organization:  Cerbelle.net
      11             :  *     Copyright:  Copyright (c) 2024, François Cerbelle
      12             :  *
      13             :  *  This source code is released for free distribution under the terms of the
      14             :  *  GNU General Public License as published by the Free Software Foundation.
      15             :  */
      16             : 
      17             : #include "oom.h"                                /* OOM simulation */
      18             : 
      19             : #ifndef __GNUC__
      20             : # ifndef __asm__
      21             : #  define __asm__ asm
      22             : # endif
      23             : # ifndef __sync_synchronize
      24             : #  define __sync_synchronize void
      25             : # endif
      26             : #endif
      27             : 
      28             : #include "debug/assert.h"                       /* libdebug's Assertions */
      29             : #include <sys/resource.h>                       /* setrlimit */
      30             : #include <errno.h>                              /* errno */
      31             : #include <string.h>                             /* strerror() */
      32             : #include <stdio.h>                              /* printf */
      33             : #include <malloc.h>                             /* malloc_trim() */
      34             : #include <stdlib.h>                             /* abort() */
      35             : #include <alloca.h>                             /* alloca() */
      36             : 
      37             : #ifndef _GNU_SOURCE
      38             : #define _GNU_SOURCE
      39             : #endif
      40             : #include <unistd.h>                             /* sysconf() */
      41             : 
      42             : /** Maximum number of fragmented blocks to allocate
      43             :  *
      44             :  * I never reached more than 350 on my systems.
      45             :  * The value needs to be less than UINT_MAX
      46             :  * */
      47             : #define RAMBLOCKS_MAX 1000
      48             : 
      49             : /** RAM eating pointer
      50             :  *
      51             :  * Used to store the huge memory block used to fill the available memory */
      52             : static void* _oomblocks[RAMBLOCKS_MAX] = {0};
      53             : 
      54             : /** getrlimit helper with abort on fail */
      55      468135 : static int checked_getrlimit(int resource, struct rlimit *rlim)
      56             : {
      57             :     /* Get current limit values */
      58      468135 :     if (getrlimit(resource, rlim) != 0) {
      59             :         /* Can occur, thus not ignored, but impossible to trigger for gcov/lcov */
      60           0 :         fprintf (stderr,"%s:%d getrlimit() failed with errno=%d %s\n",
      61           0 :                  __FILE__,__LINE__, errno,strerror(errno));
      62           0 :         abort();
      63             :     }
      64      468135 :     return 0;
      65             : }
      66             : 
      67             : /** Disabled fill function
      68             :  *
      69             :  * Used as oomtest_fill when disabled. Thus, the same test can be surrounded by
      70             :  * fill/free, but the actual RAM pressure will or will not be activated.
      71             :  *
      72             :  * \sa oomtest_fill
      73             :  * \sa oomtest_free
      74             :  * */
      75        1121 : static size_t oomtest_fill_preinit(const size_t minHeap, const size_t minStack)
      76             : {
      77             :     (void)minHeap;
      78             :     (void)minStack;
      79        1121 :     ASSERT(NULL==_oomblocks[0]);
      80        1121 :     return 0;
      81             : }
      82             : 
      83             : /* Documented in header file */
      84             : size_t (*oomtest_fill)(const size_t minHeap, const size_t minStack)=oomtest_fill_preinit;
      85             : 
      86             : /** Disabled free function
      87             :  *
      88             :  * Used as oomtest_free when disabled. Thus, the same test can be surrounded by
      89             :  * fill/free, but the actual RAM pressure will or will not be activated.
      90             :  *
      91             :  * \sa oomtest_free
      92             :  * \sa oomtest_fill
      93             :  * */
      94        1024 : static void oomtest_free_preinit()
      95             : {
      96        1024 :     ASSERT(NULL==_oomblocks[0]);
      97        1024 : }
      98             : 
      99             : /* Documented in header file */
     100             : void (*oomtest_free)()=oomtest_free_preinit;
     101             : 
     102             : /** Disabled oomtest_enable function
     103             :  *
     104             :  * Used as oomtest_enable before oomtest_config was invoked, which should never
     105             :  * happen and will fail to detect mistakes in the test code.
     106             :  *
     107             :  * \sa oomtest_enable
     108             :  * \sa oomtest_disable
     109             :  * */
     110           8 : static size_t oomtest_enable_preinit(const size_t softlimit)
     111             : {
     112             :     (void)softlimit;
     113             : 
     114             :     /* Should not be called without configuration */
     115           8 :     fprintf(stderr,"%s:%d oomtest_enable called without oomtest_config before\n",__FILE__,__LINE__);
     116           8 :     abort();
     117             : }
     118             : 
     119             : /* Documented in header file */
     120             : size_t (*oomtest_enable)(const size_t softlimit)=oomtest_enable_preinit;
     121             : 
     122             : /** Disabled oomtest_disable function
     123             :  *
     124             :  * Used as oomtest_disable before oomtest_config was invoked, which should never
     125             :  * happen and will fail to detect mistakes in the test code.
     126             :  *
     127             :  * \sa oomtest_enable
     128             :  * \sa oomtest_disable
     129             :  * */
     130           2 : static size_t oomtest_disable_preinit()
     131             : {
     132             :     /* Should not be called before configuration */
     133           2 :     fprintf(stderr,"%s:%d oomtest_disable called without oomtest_config before\n",__FILE__,__LINE__);
     134           2 :     abort();
     135             : }
     136             : 
     137             : /* Documented in header file */
     138             : size_t (*oomtest_disable)()=oomtest_disable_preinit;
     139             : 
     140             : /** Finds biggest available RAM block and allocate it
     141             :  *
     142             :  * This functions tries to find and allocate the biggest available memory block
     143             :  * by dichotomy (Olog2 complexity) between 0 and rlim_cur soft limit. It returns
     144             :  * the pointer.
     145             :  *
     146             :  * \param [in,out] The allocated pointer if successfull, NULL otherwise
     147             :  *
     148             :  * \return The size of the allocated block, 0 if not block was allocated.
     149             :  *
     150             :  * */
     151      409979 : static size_t oomtest_getbiggestblock(void** p_ramblock)
     152             : {
     153             :     /* This function could receive an optimized "max" value and return the final
     154             :      * "max" value to the caller. So, it could be used as the next invocation
     155             :      * starting "max" value. This would save few loop iterations, at the cost of
     156             :      * extra stack usage, I chose to not pass this value as a parameter to avoid
     157             :      * consuming stack.
     158             :      * The function starts to search between 0..rlim_cur which has very little
     159             :      * impact given the Olog2 complexity. It could be optimized and start to
     160             :      * search between 0..sysconf(AVPHYSPAGE*PAGESIZE), assuming that sysconf is
     161             :      * faster than few loop iterations. */
     162             : 
     163             :     /* Use static to avoid stack allocation/free */
     164             :     static size_t max,cur;
     165             :     static struct rlimit limit;
     166             : 
     167      409979 :     ASSERT(NULL!=p_ramblock);
     168      409979 :     ASSERT(NULL==*p_ramblock);
     169             : 
     170             :     /* Get the current limits */
     171      409979 :     checked_getrlimit(RLIMIT_AS, &limit);
     172      409979 :     max = limit.rlim_cur;
     173             : 
     174             :     /* Restart the whole process if cur can not be allocated at the end */
     175      824007 :     while ((max>0)&&(NULL==*p_ramblock)) {
     176             :         static size_t min;
     177             :         /* Iterate quickly (Olog2) to converge to the biggest available RAM block */
     178      414028 :         min = 0;
     179     9701276 :         while (max>min) {
     180     9287248 :             cur = min+(max-min)/2; /* To avoid overflow */
     181     9287248 :             if (NULL==(*p_ramblock = malloc(cur))) {
     182     7542841 :                 max = cur;
     183             :             } else {
     184     1744407 :                 min = cur+1;
     185     1744407 :                 free(*p_ramblock);
     186             :             }
     187             :         }
     188      414028 :         cur -= 1;
     189      414028 :         *p_ramblock=malloc(cur);
     190             :     }
     191             : 
     192      409979 :     return cur;
     193             : }
     194             : 
     195             : /** Enabled fill function
     196             :  *
     197             :  * Used as oomtest_fill when enabled.
     198             :  *
     199             :  * \sa oomtest_fill
     200             :  * \sa oomtest_free
     201             :  * */
     202       10994 : static size_t oomtest_fill_postinit(const size_t minHeap, const size_t minStack)
     203             : {
     204             :     unsigned int l_numblock;
     205             :     size_t l_sum;
     206             :     void* volatile l_reservedheap;
     207             :     void* l_reservedstack;
     208             : 
     209             :     /* Probably a mistake in the test code, do not accept despite we could */
     210       10994 :     if (NULL!=_oomblocks[0]) {
     211             :         /* Dirty hack to free some RAM and allow abort to SIGABRT */
     212          30 :         free(_oomblocks[0]);
     213          30 :         fprintf (stderr,"%s:%d oomblocks are already allocated\n", __FILE__,__LINE__);
     214          30 :         abort();
     215             :     }
     216             : 
     217             :     /* Reserve/Protect stack bytes which will be auto freed at return */
     218             :     /* A failure means a stackoverflow, which is not recoverable and will abort
     219             :      * the process anyway. */
     220       10964 :     if (0<minStack)
     221       10964 :         l_reservedstack=alloca(minStack);
     222             :     (void)l_reservedstack;
     223             : 
     224             :     /* Reserve/Protect heap bytes which will be released before return */
     225       10964 :     l_reservedheap=NULL;
     226       10964 :     if (0<minHeap)
     227        9975 :         if (NULL==(l_reservedheap=malloc(minHeap))) {
     228             :             /* This will fail if minHeap is higher than rlimit */
     229           2 :             fprintf(stderr,"%s:%d Failed to reserve minheap bytes\n",__FILE__,__LINE__);
     230           2 :             abort();
     231             :         }
     232             : 
     233             :     /* Find and allocate the biggest available RAM blocks until no mre RAM
     234             :      * available or all _oomblocks are allocated */
     235       10962 :     l_numblock=0;
     236       10962 :     l_sum = 0;
     237       10962 :     l_sum += oomtest_getbiggestblock(&(_oomblocks[l_numblock++]));
     238      409979 :     while ((NULL!=_oomblocks[l_numblock-1])&&((RAMBLOCKS_MAX-1)>l_numblock))
     239      399017 :         l_sum += oomtest_getbiggestblock(&(_oomblocks[l_numblock++]));
     240             :     /* Either already NULL, which stopped the while loop or reached the last
     241             :      * slot in the table, which stopped the while loop and the slot has to be
     242             :      * set to NULL */
     243       10962 :     _oomblocks[l_numblock]=NULL;
     244             : 
     245             :     /* There can be less than 4 bytes available at this point !!! */
     246             : 
     247             :     /* Make the protected heap bytes available again */
     248       10962 :     if (NULL!=l_reservedheap)
     249        9973 :         free(l_reservedheap);
     250             : 
     251       10962 :     return l_sum;
     252             : }
     253             : 
     254             : /** Enabled free function
     255             :  *
     256             :  * Used as oomtest_free when enabled
     257             :  *
     258             :  * \sa oomtest_free
     259             :  * \sa oomtest_fill
     260             :  * */
     261       10936 : static void oomtest_free_postinit()
     262             : {
     263             :     unsigned int l_i;
     264             : 
     265             :     /* Despite we could manage, abort to help detecting mistakes in test code */
     266       10936 :     if (NULL==_oomblocks[0]) {
     267           4 :         fprintf(stderr,"%s:%d no blocks to free in oomtest_free.\n",__FILE__,__LINE__);
     268           4 :         abort();
     269             :     }
     270             : 
     271             :     /* Actually free allocated blocks and set their pointer to NULL */
     272       10932 :     l_i = 0;
     273      409596 :     while ((l_i<(RAMBLOCKS_MAX-1))&&(_oomblocks[l_i])) {
     274      398664 :         free(_oomblocks[l_i]);
     275      398664 :         _oomblocks[l_i] = NULL;
     276      398664 :         l_i+=1;
     277             :     }
     278             :     /* These barrier protections are probably useless */
     279       10932 :     malloc_trim(0);
     280             :     /* SW barrier (compiler only) */
     281       10932 :     __asm__ volatile("": : :"memory");
     282             :     /* HW barrier (CPU instruction) */
     283       10932 :     __sync_synchronize();
     284       10932 : }
     285             : 
     286             : /** Enabled oomtest_enable function
     287             :  *
     288             :  * Used as oomtest_enable after oomtest_config was invoked
     289             :  *
     290             :  * \sa oomtest_enable
     291             :  * \sa oomtest_disable
     292             :  * */
     293       36629 : static size_t oomtest_enable_postinit(const size_t softlimit)
     294             : {
     295             :     struct rlimit limit;
     296       36629 :     size_t l_softlimit = softlimit;
     297             : 
     298             :     /* Probably a bug in oomtest */
     299       36629 :     ASSERT(NULL==_oomblocks[0]);
     300             : 
     301             :     /* Get current limit values */
     302       36629 :     checked_getrlimit(RLIMIT_AS, &limit);
     303             : 
     304             :     /* Check the requested value */
     305       36629 :     if (0==l_softlimit) {
     306             :         /* Defaults to available physical RAM if requested 0 */
     307       14986 :         l_softlimit = limit.rlim_max;
     308             :     };
     309             : 
     310             :     /* Soft limit available RAM */
     311       36629 :     limit.rlim_cur = l_softlimit;
     312             : 
     313             :     /* Abort if setrlimit fails to avoid RAM bombing */
     314       36629 :     if (setrlimit(RLIMIT_AS, &limit) != 0) {
     315           4 :         fprintf (stderr,"%s:%d setrlimit(cur=%lu, max=%lu) with errno=%d %s\n",
     316             :                  __FILE__,__LINE__,
     317           2 :                  (unsigned long)limit.rlim_cur, (unsigned long)limit.rlim_max,
     318           2 :                  errno,strerror(errno));
     319           2 :         abort();
     320             :     }
     321             : 
     322             :     /* Activate fill/free functions */
     323       36627 :     oomtest_fill = oomtest_fill_postinit;
     324       36627 :     oomtest_free = oomtest_free_postinit;
     325             : 
     326             :     /* Return the actual current soft limit */
     327       36627 :     return limit.rlim_cur;
     328             : }
     329             : 
     330             : /** Enabled oomtest_disable function
     331             :  *
     332             :  * Used as oomtest_disable after oomtest_config was invoked
     333             :  *
     334             :  * \sa oomtest_enable
     335             :  * \sa oomtest_disable
     336             :  * */
     337       14989 : static size_t oomtest_disable_postinit()
     338             : {
     339             :     struct rlimit limit;
     340             : 
     341       14989 :     if (NULL!=_oomblocks[0]) {
     342           0 :         fprintf(stderr,"%s:%d RAM still allocated while calling oomtest_disable\n",__FILE__,__LINE__);
     343           0 :         abort();
     344             :     }
     345             : 
     346             :     /* Do not allow calling disable if not enabled to detect test mistakes */
     347       14989 :     if ((oomtest_fill!=oomtest_fill_postinit)||(oomtest_free!=oomtest_free_postinit)) {
     348           6 :         fprintf(stderr,"%s:%d Impossible to disable oomtest if not previously enabled\n",__FILE__,__LINE__);
     349           6 :         abort();
     350             :     }
     351             : 
     352             :     /* Reset the soft limit to hard limit */
     353       14983 :     oomtest_enable_postinit(0);
     354             : 
     355             :     /* Get current limit values */
     356       14983 :     checked_getrlimit(RLIMIT_AS, &limit);
     357             : 
     358             :     /* Restore disabled functors */
     359       14983 :     oomtest_fill = oomtest_fill_preinit;
     360       14983 :     oomtest_free = oomtest_free_preinit;
     361             : 
     362             :     /* Return the actual current soft limit, which is equal to hard limit */
     363       14983 :     return limit.rlim_cur;
     364             : }
     365             : 
     366             : /* Documented in header file */
     367        6542 : size_t oomtest_config(const size_t hardlimit)
     368             : {
     369             :     struct rlimit limit;
     370             :     size_t l_avail;
     371        6542 :     size_t l_hardlimit = hardlimit;
     372             : 
     373             :     /* Probably a test implementation mistake */
     374        6542 :     if (NULL!=_oomblocks[0]) {
     375           0 :         fprintf(stderr,"%s:%d Calling oomtest_config with allocated RAM blocks is not allowed.\n",
     376             :                 __FILE__,__LINE__);
     377           0 :         abort();
     378             :     }
     379             : 
     380             :     /* Find *installed* physical RAM, 0 in case of failure */
     381        6542 :     l_avail = (sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE));
     382             : 
     383             :     /* Get current limit values */
     384        6542 :     checked_getrlimit(RLIMIT_AS, &limit);
     385        6542 :     if (0==l_hardlimit) {
     386             :         /* Defaults to available physical RAM or already set rlimit
     387             :          * if 0 requested */
     388           3 :         l_hardlimit=(limit.rlim_max<l_avail?limit.rlim_max:l_avail);
     389        6539 :     } else if (l_hardlimit>l_avail) {
     390             :         /* Fails if request is over available physical RAM to avoir swapping */
     391           2 :         fprintf(stderr,"%s:%d Requesing a limit %lu bigger than *installed* RAM %lu is not allowed.\n",
     392             :                 __FILE__,__LINE__,
     393             :                 (unsigned long)l_hardlimit,(unsigned long)l_avail);
     394           2 :         abort();
     395             :     }
     396             : 
     397             :     /* Hard limit available RAM to hardlimit globally with no way back */
     398        6540 :     limit.rlim_cur = l_hardlimit;
     399        6540 :     limit.rlim_max = l_hardlimit;
     400             : 
     401             :     /* Abort if setrlimit fails to avoid RAM bombing */
     402        6540 :     if (setrlimit(RLIMIT_AS, &limit) != 0) {
     403           4 :         fprintf (stderr,"%s:%d setrlimit(cur=%lu, max=%lu) with errno=%d %s\n",
     404           2 :                  __FILE__,__LINE__, (unsigned long)limit.rlim_cur,
     405           2 :                  (unsigned long)limit.rlim_max, errno,strerror(errno));
     406             :         /* Get current limit values */
     407           2 :         checked_getrlimit(RLIMIT_AS, &limit);
     408           2 :         fprintf (stderr,"%s:%d getrlimit() is cur=%lu, max=%lu\n",
     409           2 :                  __FILE__,__LINE__, (unsigned long)limit.rlim_cur,
     410           2 :                  (unsigned long)limit.rlim_max);
     411           2 :         abort();
     412             :     }
     413             : 
     414             :     /* Activate enable/disable functions */
     415        6538 :     oomtest_enable = oomtest_enable_postinit;
     416        6538 :     oomtest_disable = oomtest_disable_postinit;
     417             : 
     418             :     /* Return the configured limit hard=soft */
     419        6538 :     return limit.rlim_max;
     420             : }
     421             : 
     422        5057 : unsigned char oomtest_enabled()
     423             : {
     424        5057 :     return (oomtest_fill==oomtest_fill_postinit?1:0);
     425             : }

Generated by: LCOV version 1.16