1/* Determine various system internal values, Linux version.
2 Copyright (C) 1996-2022 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library 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 GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
18
19#include <array_length.h>
20#include <assert.h>
21#include <ctype.h>
22#include <dirent.h>
23#include <errno.h>
24#include <ldsodefs.h>
25#include <limits.h>
26#include <not-cancel.h>
27#include <stdio.h>
28#include <stdio_ext.h>
29#include <sys/mman.h>
30#include <sys/sysinfo.h>
31#include <sysdep.h>
32
33int
34__get_nprocs_sched (void)
35{
36 enum
37 {
38 max_num_cpus = 32768,
39 cpu_bits_size = CPU_ALLOC_SIZE (32768)
40 };
41
42 /* This cannot use malloc because it is used on malloc initialization. */
43 __cpu_mask cpu_bits[cpu_bits_size / sizeof (__cpu_mask)];
44 int r = INTERNAL_SYSCALL_CALL (sched_getaffinity, 0, cpu_bits_size,
45 cpu_bits);
46 if (r > 0)
47 return CPU_COUNT_S (cpu_bits_size, (cpu_set_t*) cpu_bits);
48 else if (r == -EINVAL)
49 /* The input buffer is still not enough to store the number of cpus. This
50 is an arbitrary values assuming such systems should be rare and there
51 is no offline cpus. */
52 return max_num_cpus;
53 /* Some other error. 2 is conservative (not a uniprocessor system, so
54 atomics are needed). */
55 return 2;
56}
57
58static char *
59next_line (int fd, char *const buffer, char **cp, char **re,
60 char *const buffer_end)
61{
62 char *res = *cp;
63 char *nl = memchr (*cp, '\n', *re - *cp);
64 if (nl == NULL)
65 {
66 if (*cp != buffer)
67 {
68 if (*re == buffer_end)
69 {
70 memmove (buffer, *cp, *re - *cp);
71 *re = buffer + (*re - *cp);
72 *cp = buffer;
73
74 ssize_t n = __read_nocancel (fd, *re, buffer_end - *re);
75 if (n < 0)
76 return NULL;
77
78 *re += n;
79
80 nl = memchr (*cp, '\n', *re - *cp);
81 while (nl == NULL && *re == buffer_end)
82 {
83 /* Truncate too long lines. */
84 *re = buffer + 3 * (buffer_end - buffer) / 4;
85 n = __read_nocancel (fd, *re, buffer_end - *re);
86 if (n < 0)
87 return NULL;
88
89 nl = memchr (*re, '\n', n);
90 **re = '\n';
91 *re += n;
92 }
93 }
94 else
95 nl = memchr (*cp, '\n', *re - *cp);
96
97 res = *cp;
98 }
99
100 if (nl == NULL)
101 nl = *re - 1;
102 }
103
104 *cp = nl + 1;
105 assert (*cp <= *re);
106
107 return res == *re ? NULL : res;
108}
109
110static int
111get_nproc_stat (char *buffer, size_t buffer_size)
112{
113 char *buffer_end = buffer + buffer_size;
114 char *cp = buffer_end;
115 char *re = buffer_end;
116
117 /* Default to an SMP system in case we cannot obtain an accurate
118 number. */
119 int result = 2;
120
121 const int flags = O_RDONLY | O_CLOEXEC;
122 int fd = __open_nocancel ("/proc/stat", flags);
123 if (fd != -1)
124 {
125 result = 0;
126
127 char *l;
128 while ((l = next_line (fd, buffer, &cp, &re, buffer_end)) != NULL)
129 /* The current format of /proc/stat has all the cpu* entries
130 at the front. We assume here that stays this way. */
131 if (strncmp (l, "cpu", 3) != 0)
132 break;
133 else if (isdigit (l[3]))
134 ++result;
135
136 __close_nocancel_nostatus (fd);
137 }
138
139 return result;
140}
141
142int
143__get_nprocs (void)
144{
145 enum { buffer_size = 1024 };
146 char buffer[buffer_size];
147 char *buffer_end = buffer + buffer_size;
148 char *cp = buffer_end;
149 char *re = buffer_end;
150
151 const int flags = O_RDONLY | O_CLOEXEC;
152 /* This file contains comma-separated ranges. */
153 int fd = __open_nocancel ("/sys/devices/system/cpu/online", flags);
154 char *l;
155 int result = 0;
156 if (fd != -1)
157 {
158 l = next_line (fd, buffer, &cp, &re, buffer_end);
159 if (l != NULL)
160 do
161 {
162 char *endp;
163 unsigned long int n = strtoul (l, &endp, 10);
164 if (l == endp)
165 {
166 result = 0;
167 break;
168 }
169
170 unsigned long int m = n;
171 if (*endp == '-')
172 {
173 l = endp + 1;
174 m = strtoul (l, &endp, 10);
175 if (l == endp)
176 {
177 result = 0;
178 break;
179 }
180 }
181
182 result += m - n + 1;
183
184 l = endp;
185 if (l < re && *l == ',')
186 ++l;
187 }
188 while (l < re && *l != '\n');
189
190 __close_nocancel_nostatus (fd);
191
192 if (result > 0)
193 return result;
194 }
195
196 return get_nproc_stat (buffer, buffer_size);
197}
198libc_hidden_def (__get_nprocs)
199weak_alias (__get_nprocs, get_nprocs)
200
201
202/* On some architectures it is possible to distinguish between configured
203 and active cpus. */
204int
205__get_nprocs_conf (void)
206{
207 /* Try to use the sysfs filesystem. It has actual information about
208 online processors. */
209 DIR *dir = __opendir ("/sys/devices/system/cpu");
210 if (dir != NULL)
211 {
212 int count = 0;
213 struct dirent64 *d;
214
215 while ((d = __readdir64 (dir)) != NULL)
216 /* NB: the sysfs has d_type support. */
217 if (d->d_type == DT_DIR && strncmp (d->d_name, "cpu", 3) == 0)
218 {
219 char *endp;
220 unsigned long int nr = strtoul (d->d_name + 3, &endp, 10);
221 if (nr != ULONG_MAX && endp != d->d_name + 3 && *endp == '\0')
222 ++count;
223 }
224
225 __closedir (dir);
226
227 return count;
228 }
229
230 enum { buffer_size = 1024 };
231 char buffer[buffer_size];
232 return get_nproc_stat (buffer, buffer_size);
233}
234libc_hidden_def (__get_nprocs_conf)
235weak_alias (__get_nprocs_conf, get_nprocs_conf)
236
237
238/* Compute (num*mem_unit)/pagesize, but avoid overflowing long int.
239 In practice, mem_unit is never bigger than the page size, so after
240 the first loop it is 1. [In the kernel, it is initialized to
241 PAGE_SIZE in mm/page_alloc.c:si_meminfo(), and then in
242 kernel.sys.c:do_sysinfo() it is set to 1 if unsigned long can
243 represent all the sizes measured in bytes]. */
244static long int
245sysinfo_mempages (unsigned long int num, unsigned int mem_unit)
246{
247 unsigned long int ps = __getpagesize ();
248
249 while (mem_unit > 1 && ps > 1)
250 {
251 mem_unit >>= 1;
252 ps >>= 1;
253 }
254 num *= mem_unit;
255 while (ps > 1)
256 {
257 ps >>= 1;
258 num >>= 1;
259 }
260 return num;
261}
262
263/* Return the number of pages of total/available physical memory in
264 the system. This used to be done by parsing /proc/meminfo, but
265 that's unnecessarily expensive (and /proc is not always available).
266 The sysinfo syscall provides the same information, and has been
267 available at least since kernel 2.3.48. */
268long int
269__get_phys_pages (void)
270{
271 struct sysinfo info;
272
273 __sysinfo (&info);
274 return sysinfo_mempages (info.totalram, info.mem_unit);
275}
276libc_hidden_def (__get_phys_pages)
277weak_alias (__get_phys_pages, get_phys_pages)
278
279long int
280__get_avphys_pages (void)
281{
282 struct sysinfo info;
283
284 __sysinfo (&info);
285 return sysinfo_mempages (info.freeram, info.mem_unit);
286}
287libc_hidden_def (__get_avphys_pages)
288weak_alias (__get_avphys_pages, get_avphys_pages)
289