havelib: Support overriding the result of AC_LIB_PREPARE_MULTILIB.
[gnulib.git] / lib / get-rusage-data.c
blob761eb5aaa5f9462b0e76336b2db41e3558d7d68b
1 /* Getter for RLIMIT_DATA.
2 Copyright (C) 2011-2017 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2011.
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.
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.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. */
18 #include <config.h>
20 /* Specification. */
21 #include "resource-ext.h"
23 /* The "data segment size" is defined as the virtual memory area of the
24 current process that contains malloc()ed memory.
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().
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.
42 Discussion per platform:
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.
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.
60 FreeBSD:
61 a) setrlimit with RLIMIT_DATA works.
62 b) The /proc/self/maps file contains a list of the virtual memory areas.
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.
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.
78 AIX:
79 a) setrlimit with RLIMIT_DATA works.
80 b) No VMA iteration API exists.
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) No VMA iteration API exists.
88 IRIX:
89 a) setrlimit with RLIMIT_DATA works.
90 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
91 get_rusage_data_via_setrlimit() works slightly better than
92 get_rusage_data_via_iterator() before the first malloc() call.
94 OSF/1:
95 a) setrlimit with RLIMIT_DATA works.
96 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
97 Both methods agree.
99 Solaris:
100 a) setrlimit with RLIMIT_DATA works.
101 b) No VMA iteration API exists.
103 Cygwin:
104 a) setrlimit with RLIMIT_DATA always fails.
105 get_rusage_data_via_setrlimit() therefore produces a wrong value.
106 b) The /proc/$pid/maps file lists only the memory areas belonging to
107 the executable and shared libraries, not the anonymous memory.
108 But the native Windows API works.
109 Note that malloc() apparently falls back on mmap() for large allocations.
111 mingw:
112 a) There is no setrlimit function.
113 b) There is no sbrk() function.
114 Note that malloc() falls back on VirtualAlloc() for large allocations.
116 BeOS, Haiku:
117 a) On BeOS, there is no setrlimit function.
118 On Haiku, setrlimit exists. RLIMIT_DATA is defined but unsupported:
119 getrlimit of RLIMIT_DATA always fails with errno = EINVAL.
120 b) There is a specific BeOS API: get_next_area_info().
124 #include <errno.h> /* errno */
125 #include <stdlib.h> /* size_t, abort, malloc, free, sbrk */
126 #include <fcntl.h> /* open, O_RDONLY */
127 #include <unistd.h> /* getpagesize, read, close */
130 /* System support for get_rusage_data_via_setrlimit(). */
132 #if HAVE_SETRLIMIT
133 # include <sys/time.h>
134 # include <sys/resource.h> /* getrlimit, setrlimit */
135 # include <sys/utsname.h>
136 # include <string.h> /* strlen, strcmp */
137 #endif
140 /* System support for get_rusage_data_via_iterator(). */
142 #include "vma-iter.h"
145 #if HAVE_SETRLIMIT && defined RLIMIT_DATA
147 # ifdef _AIX
148 # define errno_expected() (errno == EINVAL || errno == EFAULT)
149 # else
150 # define errno_expected() (errno == EINVAL)
151 # endif
153 static uintptr_t
154 get_rusage_data_via_setrlimit (void)
156 uintptr_t result;
158 struct rlimit orig_limit;
160 # ifdef __hpux
161 /* On HP-UX 11.00, setrlimit() RLIMIT_DATA of does not work: It cannot
162 restore the previous limits.
163 On HP-UX 11.11, setrlimit() RLIMIT_DATA of does not work: It sometimes
164 has no effect on the next sbrk() call. */
166 struct utsname buf;
168 if (uname (&buf) == 0
169 && strlen (buf.release) >= 5
170 && (strcmp (buf.release + strlen (buf.release) - 5, "11.00") == 0
171 || strcmp (buf.release + strlen (buf.release) - 5, "11.11") == 0))
172 return 0;
174 # endif
176 /* Record the original limit. */
177 if (getrlimit (RLIMIT_DATA, &orig_limit) < 0)
178 return 0;
180 if (orig_limit.rlim_max != RLIM_INFINITY
181 && (orig_limit.rlim_cur == RLIM_INFINITY
182 || orig_limit.rlim_cur > orig_limit.rlim_max))
183 /* We may not be able to restore the current rlim_cur value.
184 So bail out. */
185 return 0;
188 /* The granularity is a single page. */
189 const intptr_t pagesize = getpagesize ();
191 uintptr_t low_bound = 0;
192 uintptr_t high_bound;
194 for (;;)
196 /* Here we know that the data segment size is >= low_bound. */
197 struct rlimit try_limit;
198 uintptr_t try_next = 2 * low_bound + pagesize;
200 if (try_next < low_bound)
201 /* Overflow. */
202 try_next = ((uintptr_t) (~ 0) / pagesize) * pagesize;
204 /* There's no point in trying a value > orig_limit.rlim_max, as
205 setrlimit would fail anyway. */
206 if (orig_limit.rlim_max != RLIM_INFINITY
207 && orig_limit.rlim_max < try_next)
208 try_next = orig_limit.rlim_max;
210 /* Avoid endless loop. */
211 if (try_next == low_bound)
213 /* try_next could not be increased. */
214 result = low_bound;
215 goto done;
218 try_limit.rlim_max = orig_limit.rlim_max;
219 try_limit.rlim_cur = try_next;
220 if (setrlimit (RLIMIT_DATA, &try_limit) == 0)
222 /* Allocate a page of memory, to compare the current data segment
223 size with try_limit.rlim_cur. */
224 void *new_page = sbrk (pagesize);
226 if (new_page != (void *)(-1))
228 /* The page could be added successfully. Free it. */
229 sbrk (- pagesize);
230 /* We know that the data segment size is
231 < try_limit.rlim_cur. */
232 high_bound = try_next;
233 break;
235 else
237 /* We know that the data segment size is
238 >= try_limit.rlim_cur. */
239 low_bound = try_next;
242 else
244 /* Here we expect only EINVAL or (on AIX) EFAULT, not EPERM. */
245 if (! errno_expected ())
246 abort ();
247 /* We know that the data segment size is
248 >= try_limit.rlim_cur. */
249 low_bound = try_next;
253 /* Here we know that the data segment size is
254 >= low_bound and < high_bound. */
255 while (high_bound - low_bound > pagesize)
257 struct rlimit try_limit;
258 uintptr_t try_next =
259 low_bound + (((high_bound - low_bound) / 2) / pagesize) * pagesize;
261 /* Here low_bound <= try_next < high_bound. */
262 try_limit.rlim_max = orig_limit.rlim_max;
263 try_limit.rlim_cur = try_next;
264 if (setrlimit (RLIMIT_DATA, &try_limit) == 0)
266 /* Allocate a page of memory, to compare the current data segment
267 size with try_limit.rlim_cur. */
268 void *new_page = sbrk (pagesize);
270 if (new_page != (void *)(-1))
272 /* The page could be added successfully. Free it. */
273 sbrk (- pagesize);
274 /* We know that the data segment size is
275 < try_limit.rlim_cur. */
276 high_bound = try_next;
278 else
280 /* We know that the data segment size is
281 >= try_limit.rlim_cur. */
282 low_bound = try_next;
285 else
287 /* Here we expect only EINVAL or (on AIX) EFAULT, not EPERM. */
288 if (! errno_expected ())
289 abort ();
290 /* We know that the data segment size is
291 >= try_limit.rlim_cur. */
292 low_bound = try_next;
296 result = low_bound;
299 done:
300 /* Restore the original rlim_cur value. */
301 if (setrlimit (RLIMIT_DATA, &orig_limit) < 0)
302 abort ();
304 return result;
307 #else
309 static uintptr_t
310 get_rusage_data_via_setrlimit (void)
312 return 0;
315 #endif
318 #if VMA_ITERATE_SUPPORTED
320 struct locals
322 uintptr_t brk_value;
323 uintptr_t data_segment_size;
326 static int
327 vma_iterate_callback (void *data, uintptr_t start, uintptr_t end,
328 unsigned int flags)
330 struct locals *lp = (struct locals *) data;
332 if (start <= lp->brk_value && lp->brk_value - 1 <= end - 1)
334 lp->data_segment_size = end - start;
335 return 1;
337 return 0;
340 static uintptr_t
341 get_rusage_data_via_iterator (void)
343 # if ((defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__) || defined __BEOS__ || defined __HAIKU__
344 /* On native Windows, there is no sbrk() function.
345 On Haiku, sbrk(0) always returns 0. */
346 static void *brk_value;
348 if (brk_value == NULL)
350 brk_value = malloc (1);
351 if (brk_value == NULL)
352 return 0;
354 # else
355 void *brk_value;
357 brk_value = sbrk (0);
358 if (brk_value == (void *)-1)
359 return 0;
360 # endif
363 struct locals l;
365 l.brk_value = (uintptr_t) brk_value;
366 l.data_segment_size = 0;
367 vma_iterate (vma_iterate_callback, &l);
369 return l.data_segment_size;
373 #else
375 static uintptr_t
376 get_rusage_data_via_iterator (void)
378 return 0;
381 #endif
384 uintptr_t
385 get_rusage_data (void)
387 #if (defined __APPLE__ && defined __MACH__) || defined __CYGWIN__ /* Mac OS X, Cygwin */
388 /* get_rusage_data_via_setrlimit() does not work.
389 Prefer get_rusage_data_via_iterator(). */
390 return get_rusage_data_via_iterator ();
391 #elif HAVE_SETRLIMIT && defined RLIMIT_DATA
392 # if defined __linux__ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined _AIX || defined __sgi || defined __osf__ || defined __sun /* Linux, FreeBSD, NetBSD, OpenBSD, AIX, IRIX, OSF/1, Solaris */
393 /* get_rusage_data_via_setrlimit() works. */
394 return get_rusage_data_via_setrlimit ();
395 # else
396 /* Prefer get_rusage_data_via_setrlimit() if it succeeds,
397 because the caller may want to use the result with setrlimit(). */
398 uintptr_t result;
400 result = get_rusage_data_via_setrlimit ();
401 if (result == 0)
402 result = get_rusage_data_via_iterator ();
403 return result;
404 # endif
405 #else
406 return get_rusage_data_via_iterator ();
407 #endif
411 #ifdef TEST
413 #include <stdio.h>
416 main ()
418 printf ("Initially: 0x%08lX 0x%08lX 0x%08lX\n",
419 get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
420 get_rusage_data ());
421 malloc (0x88);
422 printf ("After small malloc: 0x%08lX 0x%08lX 0x%08lX\n",
423 get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
424 get_rusage_data ());
425 malloc (0x8812);
426 printf ("After medium malloc: 0x%08lX 0x%08lX 0x%08lX\n",
427 get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
428 get_rusage_data ());
429 malloc (0x281237);
430 printf ("After large malloc: 0x%08lX 0x%08lX 0x%08lX\n",
431 get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
432 get_rusage_data ());
433 return 0;
436 #endif /* TEST */