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