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

Generated by: LCOV version 1.16