1 /* Getter for RLIMIT_AS.
2 Copyright (C) 2011-2012 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 <http://www.gnu.org/licenses/>. */
17
18 #include <config.h>
19
20 /* Specification. */
21 #include "resource-ext.h"
22
23 /* The "address space size" is defined as the total size of the virtual memory
24 areas of the current process. This includes
25 - areas belonging to the executable and shared libraries,
26 - areas allocated by malloc() or mmap(),
27 - the stack and environment areas,
28 - gaps and guard pages (mappings with PROT_NONE),
29 - other system dependent areas, such as vsyscall or vdso on Linux.
30
31 There are two ways of retrieving the current address space size:
32 a) by trying setrlimit with various values and observing whether the
33 kernel allows additional mmap calls,
34 b) by using system dependent APIs that allow to iterate over the list
35 of virtual memory areas.
36 We don't use the mincore() based approach here, because it would be very
37 slow when applied to an entire address space, especially on 64-bit
38 platforms.
39 We define two functions
40 get_rusage_as_via_setrlimit(),
41 get_rusage_as_via_iterator().
42
43 Discussion per platform:
44
45 Linux:
46 a) setrlimit with RLIMIT_AS works.
47 b) The /proc/self/maps file contains a list of the virtual memory areas.
48 Both methods agree, except that on x86_64 systems, the value of
49 get_rusage_as_via_iterator() is 4 KB higher than
50 get_rusage_as_via_setrlimit().
51
52 MacOS X:
53 a) setrlimit with RLIMIT_AS succeeds but does not really work: The OS
54 ignores RLIMIT_AS. mmap() of a page always succeeds, therefore
55 get_rusage_as_via_setrlimit() is always 0.
56 b) The Mach based API works.
57
58 FreeBSD:
59 a) setrlimit with RLIMIT_AS works.
60 b) The /proc/self/maps file contains a list of the virtual memory areas.
61
62 NetBSD:
63 a) setrlimit with RLIMIT_AS works.
64 b) The /proc/self/maps file contains a list of the virtual memory areas.
65 Both methods agree,
66
67 OpenBSD:
68 a) setrlimit exists, but RLIMIT_AS is not defined.
69 b) mquery() can be used to find out about the virtual memory areas.
70
71 AIX:
72 a) setrlimit with RLIMIT_AS succeeds but does not really work: The OS
73 apparently ignores RLIMIT_AS. mmap() of a page always succeeds,
74 therefore get_rusage_as_via_setrlimit() is always 0.
75 b) No VMA iteration API exists.
76
77 HP-UX:
78 a) setrlimit with RLIMIT_AS works.
79 b) No VMA iteration API exists.
80
81 IRIX:
82 a) setrlimit with RLIMIT_AS works.
83 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
84 Both methods agree,
85
86 OSF/1:
87 a) setrlimit with RLIMIT_AS works.
88 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
89 The value returned by get_rusage_as_via_setrlimit() is 64 KB higher than
90 get_rusage_as_via_iterator(). It's not clear why.
91
92 Solaris:
93 a) setrlimit with RLIMIT_AS works.
94 b) No VMA iteration API exists.
95
96 Cygwin:
97 a) setrlimit with RLIMIT_AS always fails when the limit is < 0x80000000.
98 get_rusage_as_via_setrlimit() therefore produces a wrong value.
99 b) The /proc/$pid/maps file lists only the memory areas belonging to
100 the executable and shared libraries, not the anonymous memory.
101 But the native Windows API works.
102
103 mingw:
104 a) There is no setrlimit function.
105 b) The native Windows API works.
106
107 BeOS, Haiku:
108 a) On BeOS, there is no setrlimit function.
109 On Haiku, setrlimit exists. RLIMIT_AS is defined but unsupported.
110 b) There is a specific BeOS API: get_next_area_info().
111 */
112
113
114 #include <errno.h> /* errno */
115 #include <stdlib.h> /* size_t, abort */
116 #include <fcntl.h> /* open, O_RDONLY */
117 #include <unistd.h> /* getpagesize, read, close */
118
119
120 /* System support for get_rusage_as_via_setrlimit(). */
121
122 #if HAVE_SETRLIMIT
123 # include <sys/time.h>
124 # include <sys/resource.h> /* getrlimit, setrlimit */
125 #endif
126
127 /* Test whether mmap() and mprotect() are available.
128 We don't use HAVE_MMAP, because AC_FUNC_MMAP would not define it on HP-UX.
129 HAVE_MPROTECT is not enough, because mingw does not have mmap() but has an
130 mprotect() function in libgcc.a. */
131 #if HAVE_SYS_MMAN_H && HAVE_MPROTECT
132 # include <fcntl.h>
133 # include <sys/types.h>
134 # include <sys/mman.h> /* mmap, munmap */
135 /* Define MAP_FILE when it isn't otherwise. */
136 # ifndef MAP_FILE
137 # define MAP_FILE 0
138 # endif
139 #endif
140
141
142 /* System support for get_rusage_as_via_iterator(). */
143
144 #include "vma-iter.h"
145
146
147 #if HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT
148
149 static inline uintptr_t
get_rusage_as_via_setrlimit(void)150 get_rusage_as_via_setrlimit (void)
151 {
152 uintptr_t result;
153
154 struct rlimit orig_limit;
155
156 # if HAVE_MAP_ANONYMOUS
157 const int flags = MAP_ANONYMOUS | MAP_PRIVATE;
158 const int fd = -1;
159 # else /* !HAVE_MAP_ANONYMOUS */
160 const int flags = MAP_FILE | MAP_PRIVATE;
161 int fd = open ("/dev/zero", O_RDONLY, 0666);
162 if (fd < 0)
163 return 0;
164 # endif
165
166 /* Record the original limit. */
167 if (getrlimit (RLIMIT_AS, &orig_limit) < 0)
168 {
169 result = 0;
170 goto done2;
171 }
172
173 if (orig_limit.rlim_max != RLIM_INFINITY
174 && (orig_limit.rlim_cur == RLIM_INFINITY
175 || orig_limit.rlim_cur > orig_limit.rlim_max))
176 /* We may not be able to restore the current rlim_cur value.
177 So bail out. */
178 {
179 result = 0;
180 goto done2;
181 }
182
183 {
184 /* The granularity is a single page. */
185 const size_t pagesize = getpagesize ();
186
187 uintptr_t low_bound = 0;
188 uintptr_t high_bound;
189
190 for (;;)
191 {
192 /* Here we know that the address space size is >= low_bound. */
193 struct rlimit try_limit;
194 uintptr_t try_next = 2 * low_bound + pagesize;
195
196 if (try_next < low_bound)
197 /* Overflow. */
198 try_next = ((uintptr_t) (~ 0) / pagesize) * pagesize;
199
200 /* There's no point in trying a value > orig_limit.rlim_max, as
201 setrlimit would fail anyway. */
202 if (orig_limit.rlim_max != RLIM_INFINITY
203 && orig_limit.rlim_max < try_next)
204 try_next = orig_limit.rlim_max;
205
206 /* Avoid endless loop. */
207 if (try_next == low_bound)
208 {
209 /* try_next could not be increased. */
210 result = low_bound;
211 goto done1;
212 }
213
214 try_limit.rlim_max = orig_limit.rlim_max;
215 try_limit.rlim_cur = try_next;
216 if (setrlimit (RLIMIT_AS, &try_limit) == 0)
217 {
218 /* Allocate a page of memory, to compare the current address space
219 size with try_limit.rlim_cur. */
220 void *new_page =
221 mmap (NULL, pagesize, PROT_READ | PROT_WRITE, flags, fd, 0);
222
223 if (new_page != (void *)(-1))
224 {
225 /* The page could be added successfully. Free it. */
226 if (munmap (new_page, pagesize) < 0)
227 abort ();
228 /* We know that the address space size is
229 < try_limit.rlim_cur. */
230 high_bound = try_next;
231 break;
232 }
233 else
234 {
235 /* We know that the address space size is
236 >= try_limit.rlim_cur. */
237 low_bound = try_next;
238 }
239 }
240 else
241 {
242 /* Here we expect only EINVAL, not EPERM. */
243 if (errno != EINVAL)
244 abort ();
245 /* We know that the address space size is
246 >= try_limit.rlim_cur. */
247 low_bound = try_next;
248 }
249 }
250
251 /* Here we know that the address space size is
252 >= low_bound and < high_bound. */
253 while (high_bound - low_bound > pagesize)
254 {
255 struct rlimit try_limit;
256 uintptr_t try_next =
257 low_bound + (((high_bound - low_bound) / 2) / pagesize) * pagesize;
258
259 /* Here low_bound <= try_next < high_bound. */
260 try_limit.rlim_max = orig_limit.rlim_max;
261 try_limit.rlim_cur = try_next;
262 if (setrlimit (RLIMIT_AS, &try_limit) == 0)
263 {
264 /* Allocate a page of memory, to compare the current address space
265 size with try_limit.rlim_cur. */
266 void *new_page =
267 mmap (NULL, pagesize, PROT_READ | PROT_WRITE, flags, fd, 0);
268
269 if (new_page != (void *)(-1))
270 {
271 /* The page could be added successfully. Free it. */
272 if (munmap (new_page, pagesize) < 0)
273 abort ();
274 /* We know that the address space size is
275 < try_limit.rlim_cur. */
276 high_bound = try_next;
277 }
278 else
279 {
280 /* We know that the address space size is
281 >= try_limit.rlim_cur. */
282 low_bound = try_next;
283 }
284 }
285 else
286 {
287 /* Here we expect only EINVAL, not EPERM. */
288 if (errno != EINVAL)
289 abort ();
290 /* We know that the address space size is
291 >= try_limit.rlim_cur. */
292 low_bound = try_next;
293 }
294 }
295
296 result = low_bound;
297 }
298
299 done1:
300 /* Restore the original rlim_cur value. */
301 if (setrlimit (RLIMIT_AS, &orig_limit) < 0)
302 abort ();
303
304 done2:
305 # if !HAVE_MAP_ANONYMOUS
306 close (fd);
307 # endif
308 return result;
309 }
310
311 #else
312
313 static inline uintptr_t
get_rusage_as_via_setrlimit(void)314 get_rusage_as_via_setrlimit (void)
315 {
316 return 0;
317 }
318
319 #endif
320
321
322 #if VMA_ITERATE_SUPPORTED
323
324 static int
vma_iterate_callback(void * data,uintptr_t start,uintptr_t end,unsigned int flags)325 vma_iterate_callback (void *data, uintptr_t start, uintptr_t end,
326 unsigned int flags)
327 {
328 uintptr_t *totalp = (uintptr_t *) data;
329
330 *totalp += end - start;
331 return 0;
332 }
333
334 static inline uintptr_t
get_rusage_as_via_iterator(void)335 get_rusage_as_via_iterator (void)
336 {
337 uintptr_t total = 0;
338
339 vma_iterate (vma_iterate_callback, &total);
340
341 return total;
342 }
343
344 #else
345
346 static inline uintptr_t
get_rusage_as_via_iterator(void)347 get_rusage_as_via_iterator (void)
348 {
349 return 0;
350 }
351
352 #endif
353
354
355 uintptr_t
get_rusage_as(void)356 get_rusage_as (void)
357 {
358 #if (defined __APPLE__ && defined __MACH__) || defined _AIX || defined __CYGWIN__ /* MacOS X, AIX, Cygwin */
359 /* get_rusage_as_via_setrlimit() does not work.
360 Prefer get_rusage_as_via_iterator(). */
361 return get_rusage_as_via_iterator ();
362 #elif HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT
363 /* Prefer get_rusage_as_via_setrlimit() if it succeeds,
364 because the caller may want to use the result with setrlimit(). */
365 uintptr_t result;
366
367 result = get_rusage_as_via_setrlimit ();
368 if (result == 0)
369 result = get_rusage_as_via_iterator ();
370 return result;
371 #else
372 return get_rusage_as_via_iterator ();
373 #endif
374 }
375
376
377 #ifdef TEST
378
379 #include <stdio.h>
380
381 int
main()382 main ()
383 {
384 printf ("Initially: 0x%08lX 0x%08lX 0x%08lX\n",
385 get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
386 get_rusage_as ());
387 malloc (0x88);
388 printf ("After small malloc: 0x%08lX 0x%08lX 0x%08lX\n",
389 get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
390 get_rusage_as ());
391 malloc (0x8812);
392 printf ("After medium malloc: 0x%08lX 0x%08lX 0x%08lX\n",
393 get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
394 get_rusage_as ());
395 malloc (0x281237);
396 printf ("After large malloc: 0x%08lX 0x%08lX 0x%08lX\n",
397 get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
398 get_rusage_as ());
399 return 0;
400 }
401
402 #endif /* TEST */
403