1 /* Getter for RLIMIT_AS.
2 Copyright (C) 2011 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/>. */
21 #include "resource-ext.h"
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.
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
39 We define two functions
40 get_rusage_as_via_setrlimit(),
41 get_rusage_as_via_iterator().
43 Discussion per platform:
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().
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.
59 a) setrlimit with RLIMIT_AS works.
60 b) The /proc/self/maps file contains a list of the virtual memory areas.
63 a) setrlimit with RLIMIT_AS works.
64 b) The /proc/self/maps file contains a list of the virtual memory areas.
68 a) setrlimit exists, but RLIMIT_AS is not defined.
69 b) No VMA iteration API exists.
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.
78 a) setrlimit with RLIMIT_AS works.
79 b) No VMA iteration API exists.
82 a) setrlimit with RLIMIT_AS works.
83 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
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.
93 a) setrlimit with RLIMIT_AS works.
94 b) No VMA iteration API exists.
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 Win32 API works.
104 a) There is no setrlimit function.
105 b) The native Win32 API works.
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().
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 */
120 /* System support for get_rusage_as_via_setrlimit(). */
123 # include <sys/time.h>
124 # include <sys/resource.h> /* getrlimit, setrlimit */
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
133 # include <sys/types.h>
134 # include <sys/mman.h> /* mmap, munmap */
135 /* Define MAP_FILE when it isn't otherwise. */
142 /* System support for get_rusage_as_via_iterator(). */
144 #if defined __sgi || defined __osf__ /* IRIX, OSF/1 */
145 # include <string.h> /* memcpy */
146 # include <sys/types.h>
147 # include <sys/procfs.h> /* PIOC*, prmap_t */
150 #if defined __APPLE__ && defined __MACH__ /* MacOS X */
151 # include <mach/mach.h>
154 #if (defined _WIN32 || defined __WIN32__) || defined __CYGWIN__ /* Windows */
155 # include <windows.h>
158 #if defined __BEOS__ /* BeOS */
163 #if HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT
165 static inline uintptr_t
166 get_rusage_as_via_setrlimit (void)
170 struct rlimit orig_limit;
172 # if HAVE_MAP_ANONYMOUS
173 const int flags = MAP_ANONYMOUS | MAP_PRIVATE;
175 # else /* !HAVE_MAP_ANONYMOUS */
176 const int flags = MAP_FILE | MAP_PRIVATE;
177 int fd = open ("/dev/zero", O_RDONLY, 0666);
182 /* Record the original limit. */
183 if (getrlimit (RLIMIT_AS, &orig_limit) < 0)
189 if (orig_limit.rlim_max != RLIM_INFINITY
190 && (orig_limit.rlim_cur == RLIM_INFINITY
191 || orig_limit.rlim_cur > orig_limit.rlim_max))
192 /* We may not be able to restore the current rlim_cur value.
200 /* The granularity is a single page. */
201 const size_t pagesize = getpagesize ();
203 uintptr_t low_bound = 0;
204 uintptr_t high_bound;
208 /* Here we know that the address space size is >= low_bound. */
209 struct rlimit try_limit;
210 uintptr_t try_next = 2 * low_bound + pagesize;
212 if (try_next < low_bound)
214 try_next = ((uintptr_t) (~ 0) / pagesize) * pagesize;
216 /* There's no point in trying a value > orig_limit.rlim_max, as
217 setrlimit would fail anyway. */
218 if (orig_limit.rlim_max != RLIM_INFINITY
219 && orig_limit.rlim_max < try_next)
220 try_next = orig_limit.rlim_max;
222 /* Avoid endless loop. */
223 if (try_next == low_bound)
225 /* try_next could not be increased. */
230 try_limit.rlim_max = orig_limit.rlim_max;
231 try_limit.rlim_cur = try_next;
232 if (setrlimit (RLIMIT_AS, &try_limit) == 0)
234 /* Allocate a page of memory, to compare the current address space
235 size with try_limit.rlim_cur. */
237 mmap (NULL, pagesize, PROT_READ | PROT_WRITE, flags, fd, 0);
239 if (new_page != (void *)(-1))
241 /* The page could be added successfully. Free it. */
242 if (munmap (new_page, pagesize) < 0)
244 /* We know that the address space size is
245 < try_limit.rlim_cur. */
246 high_bound = try_next;
251 /* We know that the address space size is
252 >= try_limit.rlim_cur. */
253 low_bound = try_next;
258 /* Here we expect only EINVAL, not EPERM. */
261 /* We know that the address space size is
262 >= try_limit.rlim_cur. */
263 low_bound = try_next;
267 /* Here we know that the address space size is
268 >= low_bound and < high_bound. */
269 while (high_bound - low_bound > pagesize)
271 struct rlimit try_limit;
273 low_bound + (((high_bound - low_bound) / 2) / pagesize) * pagesize;
275 /* Here low_bound <= try_next < high_bound. */
276 try_limit.rlim_max = orig_limit.rlim_max;
277 try_limit.rlim_cur = try_next;
278 if (setrlimit (RLIMIT_AS, &try_limit) == 0)
280 /* Allocate a page of memory, to compare the current address space
281 size with try_limit.rlim_cur. */
283 mmap (NULL, pagesize, PROT_READ | PROT_WRITE, flags, fd, 0);
285 if (new_page != (void *)(-1))
287 /* The page could be added successfully. Free it. */
288 if (munmap (new_page, pagesize) < 0)
290 /* We know that the address space size is
291 < try_limit.rlim_cur. */
292 high_bound = try_next;
296 /* We know that the address space size is
297 >= try_limit.rlim_cur. */
298 low_bound = try_next;
303 /* Here we expect only EINVAL, not EPERM. */
306 /* We know that the address space size is
307 >= try_limit.rlim_cur. */
308 low_bound = try_next;
316 /* Restore the original rlim_cur value. */
317 if (setrlimit (RLIMIT_AS, &orig_limit) < 0)
321 # if !HAVE_MAP_ANONYMOUS
330 /* Support for reading text files in the /proc file system. */
332 #if defined __linux__ || defined __FreeBSD__ || defined __NetBSD__ /* || defined __CYGWIN__ */
334 /* Buffered read-only streams.
335 We cannot use <stdio.h> here, because fopen() calls malloc(), and a malloc()
336 call may call mmap() and thus pre-allocate available memory. */
347 /* Open a read-only file stream. */
349 rof_open (struct rofile *rof, const char *filename)
351 int fd = open (filename, O_RDONLY);
361 /* Return the next byte from a read-only file stream without consuming it,
364 rof_peekchar (struct rofile *rof)
366 if (rof->position == rof->filled)
373 int n = read (rof->fd, rof->buffer, sizeof (rof->buffer));
375 if (n < 0 && errno == EINTR)
388 return (unsigned char) rof->buffer[rof->position];
391 /* Return the next byte from a read-only file stream, or -1 at EOF. */
393 rof_getchar (struct rofile *rof)
395 int c = rof_peekchar (rof);
401 /* Parse an unsigned hexadecimal number from a read-only file stream. */
403 rof_scanf_lx (struct rofile *rof, unsigned long *valuep)
405 unsigned long value = 0;
406 unsigned int numdigits = 0;
409 int c = rof_peekchar (rof);
410 if (c >= '0' && c <= '9')
411 value = (value << 4) + (c - '0');
412 else if (c >= 'A' && c <= 'F')
413 value = (value << 4) + (c - 'A' + 10);
414 else if (c >= 'a' && c <= 'f')
415 value = (value << 4) + (c - 'a' + 10);
427 /* Close a read-only file stream. */
429 rof_close (struct rofile *rof)
437 static inline uintptr_t
438 get_rusage_as_via_iterator (void)
440 #if defined __linux__ /* || defined __CYGWIN__ */
446 /* Open the current process' maps file. It describes one VMA per line. */
447 if (rof_open (&rof, "/proc/self/maps") < 0)
453 unsigned long start, end;
455 if (!(rof_scanf_lx (&rof, &start) >= 0
456 && rof_getchar (&rof) == '-'
457 && rof_scanf_lx (&rof, &end) >= 0))
459 while (c = rof_getchar (&rof), c != -1 && c != '\n')
461 total += end - start;
466 #elif defined __FreeBSD__ || defined __NetBSD__
472 /* Open the current process' maps file. It describes one VMA per line. */
473 if (rof_open (&rof, "/proc/curproc/map") < 0)
479 unsigned long start, end;
481 if (!(rof_getchar (&rof) == '0'
482 && rof_getchar (&rof) == 'x'
483 && rof_scanf_lx (&rof, &start) >= 0))
485 while (c = rof_peekchar (&rof), c == ' ' || c == '\t')
487 if (!(rof_getchar (&rof) == '0'
488 && rof_getchar (&rof) == 'x'
489 && rof_scanf_lx (&rof, &end) >= 0))
491 while (c = rof_getchar (&rof), c != -1 && c != '\n')
493 total += end - start;
498 #elif defined __sgi || defined __osf__ /* IRIX, OSF/1 */
501 char fnamebuf[6+10+1];
506 # if HAVE_MAP_ANONYMOUS
508 # define map_flags MAP_ANONYMOUS
514 unsigned long auxmap_start;
515 unsigned long auxmap_end;
520 pagesize = getpagesize ();
522 /* Construct fname = sprintf (fnamebuf+i, "/proc/%u", getpid ()). */
523 fname = fnamebuf + sizeof (fnamebuf) - 1;
526 unsigned int value = getpid ();
528 *--fname = (value % 10) + '0';
529 while ((value = value / 10) > 0);
532 memcpy (fname, "/proc/", 6);
534 fd = open (fname, O_RDONLY);
538 if (ioctl (fd, PIOCNMAP, &nmaps) < 0)
541 memneed = (nmaps + 10) * sizeof (prmap_t);
542 /* Allocate memneed bytes of memory.
543 We cannot use alloca here, because not much stack space is guaranteed.
544 We also cannot use malloc here, because a malloc() call may call mmap()
545 and thus pre-allocate available memory.
546 So use mmap(), and ignore the resulting VMA. */
547 memneed = ((memneed - 1) / pagesize + 1) * pagesize;
548 # if !HAVE_MAP_ANONYMOUS
549 zero_fd = open ("/dev/zero", O_RDONLY, 0644);
553 auxmap = (void *) mmap ((void *) 0, memneed, PROT_READ | PROT_WRITE,
554 map_flags | MAP_PRIVATE, zero_fd, 0);
555 # if !HAVE_MAP_ANONYMOUS
558 if (auxmap == (void *) -1)
560 auxmap_start = (unsigned long) auxmap;
561 auxmap_end = auxmap_start + memneed;
562 maps = (prmap_t *) auxmap;
564 if (ioctl (fd, PIOCMAP, maps) < 0)
570 unsigned long start, end;
572 start = (unsigned long) mp->pr_vaddr;
573 end = start + mp->pr_size;
574 if (start == 0 && end == 0)
577 if (start <= auxmap_start && auxmap_end - 1 <= end - 1)
578 /* Consider [start,end-1] \ [auxmap_start,auxmap_end-1]
579 = [start,auxmap_start-1] u [auxmap_end,end-1]. */
580 total += (end - start) - memneed;
582 total += end - start;
584 munmap (auxmap, memneed);
589 munmap (auxmap, memneed);
594 #elif defined __APPLE__ && defined __MACH__ /* MacOS X */
596 task_t task = mach_task_self ();
597 vm_address_t address;
599 vm_address_t total = 0;
601 for (address = VM_MIN_ADDRESS;; address += size)
604 mach_port_t object_name;
605 /* In MacOS X 10.5, the types vm_address_t, vm_offset_t, vm_size_t have
606 32 bits in 32-bit processes and 64 bits in 64-bit processes. Whereas
607 mach_vm_address_t and mach_vm_size_t are always 64 bits large.
608 MacOS X 10.5 has three vm_region like methods:
609 - vm_region. It has arguments that depend on whether the current
610 process is 32-bit or 64-bit. When linking dynamically, this
611 function exists only in 32-bit processes. Therefore we use it only
613 - vm_region_64. It has arguments that depend on whether the current
614 process is 32-bit or 64-bit. It interprets a flavor
615 VM_REGION_BASIC_INFO as VM_REGION_BASIC_INFO_64, which is
616 dangerous since 'struct vm_region_basic_info_64' is larger than
617 'struct vm_region_basic_info'; therefore let's write
618 VM_REGION_BASIC_INFO_64 explicitly.
619 - mach_vm_region. It has arguments that are 64-bit always. This
620 function is useful when you want to access the VM of a process
621 other than the current process.
622 In 64-bit processes, we could use vm_region_64 or mach_vm_region.
623 I choose vm_region_64 because it uses the same types as vm_region,
624 resulting in less conditional code. */
625 # if defined __ppc64__ || defined __x86_64__
626 struct vm_region_basic_info_64 info;
627 mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
629 more = (vm_region_64 (task, &address, &size, VM_REGION_BASIC_INFO_64,
630 (vm_region_info_t)&info, &info_count, &object_name)
633 struct vm_region_basic_info info;
634 mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT;
636 more = (vm_region (task, &address, &size, VM_REGION_BASIC_INFO,
637 (vm_region_info_t)&info, &info_count, &object_name)
640 if (object_name != MACH_PORT_NULL)
641 mach_port_deallocate (mach_task_self (), object_name);
648 #elif (defined _WIN32 || defined __WIN32__) || defined __CYGWIN__
649 /* Windows platform. Use the native Windows API. */
651 MEMORY_BASIC_INFORMATION info;
652 unsigned long address = 0;
653 unsigned long total = 0;
655 while (VirtualQuery ((void*)address, &info, sizeof(info)) == sizeof(info))
657 if (info.State != MEM_FREE)
658 /* Ignore areas where info.Protect has the undocumented value 0.
659 This is needed, so that on Cygwin, areas used by malloc() are
660 distinguished from areas reserved for future malloc(). */
661 if (info.Protect != 0)
662 total += info.RegionSize;
663 address = (unsigned long)info.BaseAddress + info.RegionSize;
667 #elif defined __BEOS__
668 /* Use the BeOS specific API. */
672 unsigned long total = 0;
675 while (get_next_area_info (0, &cookie, &info) == B_OK)
677 unsigned long start, end;
679 start = (unsigned long) info.address;
680 end = start + info.size;
682 total += end - start;
697 #if (defined __APPLE__ && defined __MACH__) || defined _AIX || defined __CYGWIN__ /* MacOS X, AIX, Cygwin */
698 /* get_rusage_as_via_setrlimit() does not work.
699 Prefer get_rusage_as_via_iterator(). */
700 return get_rusage_as_via_iterator ();
701 #elif HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT
702 /* Prefer get_rusage_as_via_setrlimit() if it succeeds,
703 because the caller may want to use the result with setrlimit(). */
706 result = get_rusage_as_via_setrlimit ();
708 result = get_rusage_as_via_iterator ();
711 return get_rusage_as_via_iterator ();