root/maint/gnulib/lib/get-rusage-data.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. get_rusage_data_via_setrlimit
  2. get_rusage_data_via_setrlimit
  3. vma_iterate_callback
  4. get_rusage_data_via_iterator
  5. get_rusage_data_via_iterator
  6. get_rusage_data
  7. main

   1 /* Getter for RLIMIT_DATA.
   2    Copyright (C) 2011-2021 Free Software Foundation, Inc.
   3    Written by Bruno Haible <bruno@clisp.org>, 2011.
   4 
   5    This program is free software: you can redistribute it and/or modify
   6    it under the terms of the GNU General Public License as published by
   7    the Free Software Foundation; either version 3 of the License, or
   8    (at your option) any later version.
   9 
  10    This program is distributed in the hope that it will be useful,
  11    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13    GNU General Public License for more details.
  14 
  15    You should have received a copy of the GNU General Public License
  16    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
  17 
  18 #include <config.h>
  19 
  20 /* Specification.  */
  21 #include "resource-ext.h"
  22 
  23 /* The "data segment size" is defined as the virtual memory area of the
  24    current process that contains malloc()ed memory.
  25 
  26    There are two ways of retrieving the current data segment size:
  27      a) by trying setrlimit with various values and observing whether the
  28         kernel allows additional sbrk() calls,
  29      b) by using system dependent APIs that allow to iterate over the list
  30         of virtual memory areas.
  31    We define two functions
  32      get_rusage_data_via_setrlimit(),
  33      get_rusage_data_via_iterator().
  34 
  35    The variant
  36      a') by trying setrlimit with various values and observing whether
  37         additional malloc() calls succeed
  38    is not as good as a), because a malloc() call can be served by already
  39    allocated memory or through mmap(), and because a malloc() of 1 page may
  40    require 2 pages.
  41 
  42    Discussion per platform:
  43 
  44    Linux:
  45      a) setrlimit with RLIMIT_DATA works.
  46      b) The /proc/self/maps file contains a list of the virtual memory areas.
  47      get_rusage_data_via_setrlimit() returns the sum of the length of the
  48      executable's data segment plus the heap VMA (an anonymous memory area),
  49      whereas get_rusage_data_via_iterator() returns only the latter.
  50      Note that malloc() falls back on mmap() for large allocations and also
  51      for small allocations if there is not enough room in the data segment.
  52 
  53    Mac OS X:
  54      a) setrlimit with RLIMIT_DATA succeeds but does not really work: The OS
  55         ignores RLIMIT_DATA. Therefore get_rusage_data_via_setrlimit() is
  56         always 0.
  57      b) The Mach based API works.
  58      Note that malloc() falls back on mmap() for large allocations.
  59 
  60    FreeBSD:
  61      a) setrlimit with RLIMIT_DATA works.
  62      b) The /proc/self/maps file contains a list of the virtual memory areas.
  63 
  64    NetBSD:
  65      a) setrlimit with RLIMIT_DATA works.
  66      b) The /proc/self/maps file contains a list of the virtual memory areas.
  67      Both methods agree.
  68      Note that malloc() uses mmap() for large allocations.
  69 
  70    OpenBSD:
  71      a) setrlimit with RLIMIT_DATA works.
  72      b) mquery() can be used to find out about the virtual memory areas.
  73      get_rusage_data_via_setrlimit() works much better than
  74      get_rusage_data_via_iterator().
  75      Note that malloc() appears to use mmap() for both large and small
  76      allocations.
  77 
  78    AIX:
  79      a) setrlimit with RLIMIT_DATA works.
  80      b) No VMA iteration API exists.
  81 
  82    HP-UX:
  83      a) setrlimit with RLIMIT_DATA works, except on HP-UX 11.00, where it
  84         cannot restore the previous limits, and except on HP-UX 11.11, where
  85         it sometimes has no effect.
  86      b) pstat_getprocvm() can be used to find out about the virtual memory
  87         areas.
  88      Both methods agree, except that the value of get_rusage_data_via_iterator()
  89      is sometimes 4 KB larger than get_rusage_data_via_setrlimit().
  90 
  91    IRIX:
  92      a) setrlimit with RLIMIT_DATA works.
  93      b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
  94      get_rusage_data_via_setrlimit() works slightly better than
  95      get_rusage_data_via_iterator() before the first malloc() call.
  96 
  97    OSF/1:
  98      a) setrlimit with RLIMIT_DATA works.
  99      b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
 100      Both methods agree.
 101 
 102    Solaris:
 103      a) setrlimit with RLIMIT_DATA works.
 104      b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP, and the
 105         /proc/self/maps file contains a list of the virtual memory areas.
 106      get_rusage_data_via_setrlimit() ignores the data segment of the executable,
 107      whereas get_rusage_data_via_iterator() includes it.
 108 
 109    Cygwin:
 110      a) setrlimit with RLIMIT_DATA always fails.
 111         get_rusage_data_via_setrlimit() therefore produces a wrong value.
 112      b) The /proc/$pid/maps file lists only the memory areas belonging to
 113         the executable and shared libraries, not the anonymous memory.
 114         But the native Windows API works.
 115      Note that malloc() apparently falls back on mmap() for large allocations.
 116 
 117    mingw:
 118      a) There is no setrlimit function.
 119      b) There is no sbrk() function.
 120      Note that malloc() falls back on VirtualAlloc() for large allocations.
 121 
 122    BeOS, Haiku:
 123      a) On BeOS, there is no setrlimit function.
 124         On Haiku, setrlimit exists. RLIMIT_DATA is defined but setrlimit fails.
 125      b) There is a specific BeOS API: get_next_area_info().
 126  */
 127 
 128 
 129 #include <errno.h> /* errno */
 130 #include <stdlib.h> /* size_t, abort, malloc, free, sbrk */
 131 #include <fcntl.h> /* open, O_RDONLY */
 132 #include <unistd.h> /* getpagesize, read, close */
 133 
 134 
 135 /* System support for get_rusage_data_via_setrlimit().  */
 136 
 137 #if HAVE_SETRLIMIT
 138 # include <sys/time.h>
 139 # include <sys/resource.h> /* getrlimit, setrlimit */
 140 # include <sys/utsname.h>
 141 # include <string.h> /* strlen, strcmp */
 142 #endif
 143 
 144 
 145 /* System support for get_rusage_data_via_iterator().  */
 146 
 147 #include "vma-iter.h"
 148 
 149 
 150 #if !(defined __APPLE__ && defined __MACH__) || defined TEST
 151 /* Implement get_rusage_data_via_setrlimit().  */
 152 
 153 # if HAVE_SETRLIMIT && defined RLIMIT_DATA && HAVE_SBRK && !defined __HAIKU__
 154 
 155 #  ifdef _AIX
 156 #   define errno_expected() (errno == EINVAL || errno == EFAULT)
 157 #  else
 158 #   define errno_expected() (errno == EINVAL)
 159 #  endif
 160 
 161 static uintptr_t
 162 get_rusage_data_via_setrlimit (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 163 {
 164   uintptr_t result;
 165 
 166   struct rlimit orig_limit;
 167 
 168 #  ifdef __hpux
 169   /* On HP-UX 11.00, setrlimit() of RLIMIT_DATA does not work: It cannot
 170      restore the previous limits.
 171      On HP-UX 11.11, setrlimit() of RLIMIT_DATA does not work: It sometimes
 172      has no effect on the next sbrk() call.  */
 173   {
 174     struct utsname buf;
 175 
 176     if (uname (&buf) == 0
 177         && strlen (buf.release) >= 5
 178         && (strcmp (buf.release + strlen (buf.release) - 5, "11.00") == 0
 179             || strcmp (buf.release + strlen (buf.release) - 5, "11.11") == 0))
 180       return 0;
 181   }
 182 #  endif
 183 
 184   /* Record the original limit.  */
 185   if (getrlimit (RLIMIT_DATA, &orig_limit) < 0)
 186     return 0;
 187 
 188   if (orig_limit.rlim_max != RLIM_INFINITY
 189       && (orig_limit.rlim_cur == RLIM_INFINITY
 190           || orig_limit.rlim_cur > orig_limit.rlim_max))
 191     /* We may not be able to restore the current rlim_cur value.
 192        So bail out.  */
 193     return 0;
 194 
 195   {
 196     /* The granularity is a single page.  */
 197     const intptr_t pagesize = getpagesize ();
 198 
 199     uintptr_t low_bound = 0;
 200     uintptr_t high_bound;
 201 
 202     for (;;)
 203       {
 204         /* Here we know that the data segment size is >= low_bound.  */
 205         struct rlimit try_limit;
 206         uintptr_t try_next = 2 * low_bound + pagesize;
 207 
 208         if (try_next < low_bound)
 209           /* Overflow.  */
 210           try_next = ((uintptr_t) (~ 0) / pagesize) * pagesize;
 211 
 212         /* There's no point in trying a value > orig_limit.rlim_max, as
 213            setrlimit would fail anyway.  */
 214         if (orig_limit.rlim_max != RLIM_INFINITY
 215             && orig_limit.rlim_max < try_next)
 216           try_next = orig_limit.rlim_max;
 217 
 218         /* Avoid endless loop.  */
 219         if (try_next == low_bound)
 220           {
 221             /* try_next could not be increased.  */
 222             result = low_bound;
 223             goto done;
 224           }
 225 
 226         try_limit.rlim_max = orig_limit.rlim_max;
 227         try_limit.rlim_cur = try_next;
 228         if (setrlimit (RLIMIT_DATA, &try_limit) == 0)
 229           {
 230             /* Allocate a page of memory, to compare the current data segment
 231                size with try_limit.rlim_cur.  */
 232             void *new_page = sbrk (pagesize);
 233 
 234             if (new_page != (void *)(-1))
 235               {
 236                 /* The page could be added successfully.  Free it.  */
 237                 sbrk (- pagesize);
 238                 /* We know that the data segment size is
 239                    < try_limit.rlim_cur.  */
 240                 high_bound = try_next;
 241                 break;
 242               }
 243             else
 244               {
 245                 /* We know that the data segment size is
 246                    >= try_limit.rlim_cur.  */
 247                 low_bound = try_next;
 248               }
 249           }
 250         else
 251           {
 252             /* Here we expect only EINVAL or (on AIX) EFAULT, not EPERM.  */
 253             if (! errno_expected ())
 254               abort ();
 255             /* We know that the data segment size is
 256                >= try_limit.rlim_cur.  */
 257             low_bound = try_next;
 258           }
 259       }
 260 
 261     /* Here we know that the data segment size is
 262        >= low_bound and < high_bound.  */
 263     while (high_bound - low_bound > pagesize)
 264       {
 265         struct rlimit try_limit;
 266         uintptr_t try_next =
 267           low_bound + (((high_bound - low_bound) / 2) / pagesize) * pagesize;
 268 
 269         /* Here low_bound <= try_next < high_bound.  */
 270         try_limit.rlim_max = orig_limit.rlim_max;
 271         try_limit.rlim_cur = try_next;
 272         if (setrlimit (RLIMIT_DATA, &try_limit) == 0)
 273           {
 274             /* Allocate a page of memory, to compare the current data segment
 275                size with try_limit.rlim_cur.  */
 276             void *new_page = sbrk (pagesize);
 277 
 278             if (new_page != (void *)(-1))
 279               {
 280                 /* The page could be added successfully.  Free it.  */
 281                 sbrk (- pagesize);
 282                 /* We know that the data segment size is
 283                    < try_limit.rlim_cur.  */
 284                 high_bound = try_next;
 285               }
 286             else
 287               {
 288                 /* We know that the data segment size is
 289                    >= try_limit.rlim_cur.  */
 290                 low_bound = try_next;
 291               }
 292           }
 293         else
 294           {
 295             /* Here we expect only EINVAL or (on AIX) EFAULT, not EPERM.  */
 296             if (! errno_expected ())
 297               abort ();
 298             /* We know that the data segment size is
 299                >= try_limit.rlim_cur.  */
 300             low_bound = try_next;
 301           }
 302       }
 303 
 304     result = low_bound;
 305   }
 306 
 307  done:
 308   /* Restore the original rlim_cur value.  */
 309   if (setrlimit (RLIMIT_DATA, &orig_limit) < 0)
 310     abort ();
 311 
 312   return result;
 313 }
 314 
 315 # else
 316 
 317 static uintptr_t
 318 get_rusage_data_via_setrlimit (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 319 {
 320   return 0;
 321 }
 322 
 323 # endif
 324 
 325 #endif
 326 
 327 
 328 #if !(defined __APPLE__ && defined __MACH__) || defined TEST
 329 /* Implement get_rusage_data_via_iterator().  */
 330 
 331 # if VMA_ITERATE_SUPPORTED
 332 
 333 struct locals
 334 {
 335   uintptr_t brk_value;
 336   uintptr_t data_segment_size;
 337 };
 338 
 339 static int
 340 vma_iterate_callback (void *data, uintptr_t start, uintptr_t end,
     /* [previous][next][first][last][top][bottom][index][help] */
 341                       unsigned int flags)
 342 {
 343   struct locals *lp = (struct locals *) data;
 344 
 345   if (start <= lp->brk_value && lp->brk_value - 1 <= end - 1)
 346     {
 347       lp->data_segment_size = end - start;
 348       return 1;
 349     }
 350   return 0;
 351 }
 352 
 353 static uintptr_t
 354 get_rusage_data_via_iterator (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 355 {
 356 #  if (defined _WIN32 && !defined __CYGWIN__) || defined __BEOS__ || defined __HAIKU__
 357   /* On native Windows, there is no sbrk() function.
 358      On Haiku, sbrk(0) always returns 0.  */
 359   static void *brk_value;
 360 
 361   if (brk_value == NULL)
 362     {
 363       brk_value = malloc (1);
 364       if (brk_value == NULL)
 365         return 0;
 366     }
 367 #  else
 368   void *brk_value;
 369 
 370   brk_value = sbrk (0);
 371   if (brk_value == (void *)-1)
 372     return 0;
 373 #  endif
 374 
 375   {
 376     struct locals l;
 377 
 378     l.brk_value = (uintptr_t) brk_value;
 379     l.data_segment_size = 0;
 380     vma_iterate (vma_iterate_callback, &l);
 381 
 382     return l.data_segment_size;
 383   }
 384 }
 385 
 386 # else
 387 
 388 static uintptr_t
 389 get_rusage_data_via_iterator (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 390 {
 391   return 0;
 392 }
 393 
 394 # endif
 395 
 396 #endif
 397 
 398 
 399 uintptr_t
 400 get_rusage_data (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 401 {
 402 #if (defined __APPLE__ && defined __MACH__) /* Mac OS X */
 403   /* get_rusage_data_via_setrlimit() does not work: it always returns 0.
 404      get_rusage_data_via_iterator() does not work: it always returns 0x400000.
 405      And sbrk() is deprecated.  */
 406   return 0;
 407 #elif defined __minix /* Minix */
 408   /* get_rusage_data_via_setrlimit() does not work: it always returns 0.
 409      get_rusage_data_via_iterator() does not work: it shrinks upon malloc. */
 410   return 0;
 411 #elif defined __CYGWIN__ /* Cygwin */
 412   /* get_rusage_data_via_setrlimit() does not work.
 413      Prefer get_rusage_data_via_iterator().  */
 414   return get_rusage_data_via_iterator ();
 415 #elif HAVE_SETRLIMIT && defined RLIMIT_DATA && !defined __HAIKU__
 416 # if defined __linux__ || defined __ANDROID__ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined _AIX || defined __hpux || defined __sgi || defined __osf__ || defined __sun /* Linux, FreeBSD, NetBSD, OpenBSD, AIX, HP-UX, IRIX, OSF/1, Solaris */
 417   /* get_rusage_data_via_setrlimit() works.  */
 418   return get_rusage_data_via_setrlimit ();
 419 # else
 420   /* Prefer get_rusage_data_via_setrlimit() if it succeeds,
 421      because the caller may want to use the result with setrlimit().  */
 422   uintptr_t result;
 423 
 424   result = get_rusage_data_via_setrlimit ();
 425   if (result == 0)
 426     result = get_rusage_data_via_iterator ();
 427   return result;
 428 # endif
 429 #else
 430   return get_rusage_data_via_iterator ();
 431 #endif
 432 }
 433 
 434 
 435 #ifdef TEST
 436 
 437 #include <stdio.h>
 438 
 439 int
 440 main ()
     /* [previous][next][first][last][top][bottom][index][help] */
 441 {
 442   printf ("Initially:           0x%08lX 0x%08lX 0x%08lX\n",
 443           get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
 444           get_rusage_data ());
 445   malloc (0x88);
 446   printf ("After small malloc:  0x%08lX 0x%08lX 0x%08lX\n",
 447           get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
 448           get_rusage_data ());
 449   malloc (0x8812);
 450   printf ("After medium malloc: 0x%08lX 0x%08lX 0x%08lX\n",
 451           get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
 452           get_rusage_data ());
 453   malloc (0x281237);
 454   printf ("After large malloc:  0x%08lX 0x%08lX 0x%08lX\n",
 455           get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
 456           get_rusage_data ());
 457   return 0;
 458 }
 459 
 460 #endif /* TEST */

/* [previous][next][first][last][top][bottom][index][help] */