1 /* Getter for RLIMIT_AS.
2 Copyright (C) 2011-2020 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 <https://www.gnu.org/licenses/>. */
20 /* On Android, when targeting Android 4.4 or older with a GCC toolchain,
21 prevent a compilation error
22 "error: call to 'mmap' declared with attribute error: mmap is not
23 available with _FILE_OFFSET_BITS=64 when using GCC until android-21.
24 Either raise your minSdkVersion, disable _FILE_OFFSET_BITS=64, or
26 The files that we access in this compilation unit are less than 2 GB
28 #if defined __ANDROID__
29 # undef _FILE_OFFSET_BITS
33 #include "resource-ext.h"
35 /* The "address space size" is defined as the total size of the virtual memory
36 areas of the current process. This includes
37 - areas belonging to the executable and shared libraries,
38 - areas allocated by malloc() or mmap(),
39 - the stack and environment areas,
40 - gaps and guard pages (mappings with PROT_NONE),
41 - other system dependent areas, such as vsyscall or vdso on Linux.
43 There are two ways of retrieving the current address space size:
44 a) by trying setrlimit with various values and observing whether the
45 kernel allows additional mmap calls,
46 b) by using system dependent APIs that allow to iterate over the list
47 of virtual memory areas.
48 We don't use the mincore() based approach here, because it would be very
49 slow when applied to an entire address space, especially on 64-bit
51 We define two functions
52 get_rusage_as_via_setrlimit(),
53 get_rusage_as_via_iterator().
55 Discussion per platform:
58 a) setrlimit with RLIMIT_AS works.
59 b) The /proc/self/maps file contains a list of the virtual memory areas.
60 Both methods agree, except that on x86_64 systems, the value of
61 get_rusage_as_via_iterator() is 4 KB higher than
62 get_rusage_as_via_setrlimit().
65 a) setrlimit with RLIMIT_AS succeeds but does not really work: The OS
66 ignores RLIMIT_AS. mmap() of a page always succeeds, therefore
67 get_rusage_as_via_setrlimit() is always 0.
68 b) The Mach based API works.
71 a) setrlimit with RLIMIT_AS works.
72 b) The /proc/self/maps file contains a list of the virtual memory areas.
75 a) setrlimit with RLIMIT_AS works.
76 b) The /proc/self/maps file contains a list of the virtual memory areas.
80 a) setrlimit exists, but RLIMIT_AS is not defined.
81 b) mquery() can be used to find out about the virtual memory areas.
84 a) setrlimit with RLIMIT_AS succeeds but does not really work: The OS
85 apparently ignores RLIMIT_AS. mmap() of a page always succeeds,
86 therefore get_rusage_as_via_setrlimit() is always 0.
87 b) No VMA iteration API exists.
90 a) setrlimit with RLIMIT_AS works.
91 b) pstat_getprocvm() can be used to find out about the virtual memory
93 Both methods agree, except that the value of get_rusage_as_via_iterator()
94 is slightly larger higher than get_rusage_as_via_setrlimit(), by 4 KB in
95 32-bit mode and by 40 KB in 64-bit mode.
98 a) setrlimit with RLIMIT_AS works.
99 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
103 a) setrlimit with RLIMIT_AS works.
104 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
105 The value returned by get_rusage_as_via_setrlimit() is 64 KB higher than
106 get_rusage_as_via_iterator(). It's not clear why.
109 a) setrlimit with RLIMIT_AS works.
110 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP, and the
111 /proc/self/maps file contains a list of the virtual memory areas.
115 a) setrlimit with RLIMIT_AS always fails when the limit is < 0x80000000.
116 get_rusage_as_via_setrlimit() therefore produces a wrong value.
117 b) The /proc/$pid/maps file lists only the memory areas belonging to
118 the executable and shared libraries, not the anonymous memory.
119 But the native Windows API works.
122 a) There is no setrlimit function.
123 b) The native Windows API works.
126 a) On BeOS, there is no setrlimit function.
127 On Haiku, setrlimit exists. RLIMIT_AS is defined but setrlimit fails.
128 b) There is a specific BeOS API: get_next_area_info().
132 #include <errno.h> /* errno */
133 #include <stdlib.h> /* size_t, abort */
134 #include <fcntl.h> /* open, O_RDONLY */
135 #include <unistd.h> /* getpagesize, read, close */
138 /* System support for get_rusage_as_via_setrlimit(). */
141 # include <sys/time.h>
142 # include <sys/resource.h> /* getrlimit, setrlimit */
145 /* Test whether mmap() and mprotect() are available.
146 We don't use HAVE_MMAP, because AC_FUNC_MMAP would not define it on HP-UX.
147 HAVE_MPROTECT is not enough, because mingw does not have mmap() but has an
148 mprotect() function in libgcc.a. */
149 #if HAVE_SYS_MMAN_H && HAVE_MPROTECT
151 # include <sys/types.h>
152 # include <sys/mman.h> /* mmap, munmap */
153 /* Define MAP_FILE when it isn't otherwise. */
160 /* System support for get_rusage_as_via_iterator(). */
162 #include "vma-iter.h"
165 #if HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT && !defined __HAIKU__
168 get_rusage_as_via_setrlimit (void)
172 struct rlimit orig_limit
;
174 # if HAVE_MAP_ANONYMOUS
175 const int flags
= MAP_ANONYMOUS
| MAP_PRIVATE
;
177 # else /* !HAVE_MAP_ANONYMOUS */
178 const int flags
= MAP_FILE
| MAP_PRIVATE
;
179 int fd
= open ("/dev/zero", O_RDONLY
, 0666);
184 /* Record the original limit. */
185 if (getrlimit (RLIMIT_AS
, &orig_limit
) < 0)
191 if (orig_limit
.rlim_max
!= RLIM_INFINITY
192 && (orig_limit
.rlim_cur
== RLIM_INFINITY
193 || orig_limit
.rlim_cur
> orig_limit
.rlim_max
))
194 /* We may not be able to restore the current rlim_cur value.
202 /* The granularity is a single page. */
203 const size_t pagesize
= getpagesize ();
205 uintptr_t low_bound
= 0;
206 uintptr_t high_bound
;
210 /* Here we know that the address space size is >= low_bound. */
211 struct rlimit try_limit
;
212 uintptr_t try_next
= 2 * low_bound
+ pagesize
;
214 if (try_next
< low_bound
)
216 try_next
= ((uintptr_t) (~ 0) / pagesize
) * pagesize
;
218 /* There's no point in trying a value > orig_limit.rlim_max, as
219 setrlimit would fail anyway. */
220 if (orig_limit
.rlim_max
!= RLIM_INFINITY
221 && orig_limit
.rlim_max
< try_next
)
222 try_next
= orig_limit
.rlim_max
;
224 /* Avoid endless loop. */
225 if (try_next
== low_bound
)
227 /* try_next could not be increased. */
232 try_limit
.rlim_max
= orig_limit
.rlim_max
;
233 try_limit
.rlim_cur
= try_next
;
234 if (setrlimit (RLIMIT_AS
, &try_limit
) == 0)
236 /* Allocate a page of memory, to compare the current address space
237 size with try_limit.rlim_cur. */
239 mmap (NULL
, pagesize
, PROT_READ
| PROT_WRITE
, flags
, fd
, 0);
241 if (new_page
!= (void *)(-1))
243 /* The page could be added successfully. Free it. */
244 if (munmap (new_page
, pagesize
) < 0)
246 /* We know that the address space size is
247 < try_limit.rlim_cur. */
248 high_bound
= try_next
;
253 /* We know that the address space size is
254 >= try_limit.rlim_cur. */
255 low_bound
= try_next
;
260 /* Here we expect only EINVAL, not EPERM. */
263 /* We know that the address space size is
264 >= try_limit.rlim_cur. */
265 low_bound
= try_next
;
269 /* Here we know that the address space size is
270 >= low_bound and < high_bound. */
271 while (high_bound
- low_bound
> pagesize
)
273 struct rlimit try_limit
;
275 low_bound
+ (((high_bound
- low_bound
) / 2) / pagesize
) * pagesize
;
277 /* Here low_bound <= try_next < high_bound. */
278 try_limit
.rlim_max
= orig_limit
.rlim_max
;
279 try_limit
.rlim_cur
= try_next
;
280 if (setrlimit (RLIMIT_AS
, &try_limit
) == 0)
282 /* Allocate a page of memory, to compare the current address space
283 size with try_limit.rlim_cur. */
285 mmap (NULL
, pagesize
, PROT_READ
| PROT_WRITE
, flags
, fd
, 0);
287 if (new_page
!= (void *)(-1))
289 /* The page could be added successfully. Free it. */
290 if (munmap (new_page
, pagesize
) < 0)
292 /* We know that the address space size is
293 < try_limit.rlim_cur. */
294 high_bound
= try_next
;
298 /* We know that the address space size is
299 >= try_limit.rlim_cur. */
300 low_bound
= try_next
;
305 /* Here we expect only EINVAL, not EPERM. */
308 /* We know that the address space size is
309 >= try_limit.rlim_cur. */
310 low_bound
= try_next
;
318 /* Restore the original rlim_cur value. */
319 if (setrlimit (RLIMIT_AS
, &orig_limit
) < 0)
323 # if !HAVE_MAP_ANONYMOUS
332 get_rusage_as_via_setrlimit (void)
340 #if VMA_ITERATE_SUPPORTED
343 vma_iterate_callback (void *data
, uintptr_t start
, uintptr_t end
,
346 uintptr_t *totalp
= (uintptr_t *) data
;
348 *totalp
+= end
- start
;
353 get_rusage_as_via_iterator (void)
357 vma_iterate (vma_iterate_callback
, &total
);
365 get_rusage_as_via_iterator (void)
376 #if (defined __APPLE__ && defined __MACH__) || defined _AIX || defined __CYGWIN__ || defined __MVS__ || defined __SANITIZE_THREAD__ /* Mac OS X, AIX, Cygwin, z/OS, gcc -fsanitize=thread */
377 /* get_rusage_as_via_setrlimit() does not work.
378 Prefer get_rusage_as_via_iterator(). */
379 return get_rusage_as_via_iterator ();
380 #elif HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT && !defined __HAIKU__
381 /* Prefer get_rusage_as_via_setrlimit() if it succeeds,
382 because the caller may want to use the result with setrlimit(). */
385 result
= get_rusage_as_via_setrlimit ();
387 result
= get_rusage_as_via_iterator ();
390 return get_rusage_as_via_iterator ();
402 printf ("Initially: 0x%08lX 0x%08lX 0x%08lX\n",
403 get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
406 printf ("After small malloc: 0x%08lX 0x%08lX 0x%08lX\n",
407 get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
410 printf ("After medium malloc: 0x%08lX 0x%08lX 0x%08lX\n",
411 get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
414 printf ("After large malloc: 0x%08lX 0x%08lX 0x%08lX\n",
415 get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),