Sign in Join
Wiki copytemporary

Accessing FreeBSD System Information: what changed

Revision 2 to 3. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11[Sysctl](https://www.freebsd.org/cgi/man.cgi?query=sysctl&sektion=3) provides an interface that allows you to read and, with appropriate privileges, set many kernel attributes in FreeBSD (the BSDs, macOS and Linux). This provides a wealth of information detailing system hardware and configuration attributes which can be useful when debugging or simply optimising your application (eg to take advantage of threads on multi-core CPU systems).
22
33The '/usr/local/share/fpcsrc/rtl/freebsd/sysctlh.inc' source file is a partial translation of the FreeBSD '/usr/include/sys/sysctl.h' file. For details of the sysctls available, start a console or X11 xterm and type 'man sysctl'. Typing 'sysctl -a \| more' will list all the available sysctls and their values.
44
55## FPsysctl()
66
77The fpSysCtl() function allows you to retrieve system information. The data returned from the function comprises integers (integer and int64), strings (AnsiStrings) and C structures (records). The function is defined in '/usr/local/share/fpcsrc/rtl/bsd/sysctl.pp' as:
88
99``` pascal
1010function FPsysctl (Name: pchar; namelen:cuint; oldp:pointer;oldlenp:psize_t; newp:pointer;newlen:size_t):cint; cdecl; external name 'sysctl';
1111```
1212
1313*name* -- a pointer to a Management Information Base (MIB) array of integers. Each element of this array must contain the correct value to read or write the particular system attribute.
1414
1515*namelen* -- the length of the array of integers in name.
1616
1717Note: if the old attribute value is not required, the next two variables can be set to Nil and 0.
1818
1919*oldp* -- a pointer to the buffer into which the value is copied.
2020
2121*oldlenp* -- the size of the oldp buffer.
2222
2323Note: if the new attribute value is not being set, the next two variables should be set to Nil and 0.
2424
2525*newp* -- a pointer to the buffer containing the new value to be set.
2626
2727*newlen* -- the size of the newp buffer.
2828
2929The size of the MIB array depends on the data to be read or written. The first element indicates the level of the information and the subsequent elements indicate the value to read. The top level names are:
3030
31-```
31+```text
3232 -------------------------------------------------------------------------------
3333 Name Value Next level names Description
3434 are found in
3535 -------------------------------------------------------------------------------
3636 #define CTL_KERN 1 sys/sysctl.h /* High kernel limits */
3737 #define CTL_VM 2 vm/vm_param.h /* Virtual memory */
3838 #define CTL_VFS 3 sys/mount.h /* File system */
3939 #define CTL_NET 4 sys/socket.h /* Networking */
4040 #define CTL_DEBUG 5 sys/sysctl.h /* Debugging */
4141 #define CTL_HW 6 sys/sysctl.h /* Generic CPU, I/O */
4242 #define CTL_MACHDEP 7 sys/sysctl.h /* Machine dependent */
4343 #define CTL_USER 8 sys/sysctl.h /* User-level */
4444 #define CTL_P1003_1B 9 sys/sysctl.h /* POSIX 1003.1B */
4545 -------------------------------------------------------------------------------
4646```
4747
4848Let's look at CTL_HW level of information. The subsequent elements are found in the system sysctl.h file and are:
4949
50-```
50+```text
5151 -------------------------------------------------------------------------------
5252 Name Value Description
5353 -------------------------------------------------------------------------------
5454 #define HW_MACHINE 1 /* string: machine class */
5555 #define HW_MODEL 2 /* string: specific machine model */
5656 #define HW_NCPU 3 /* int: number of cpus */
5757 #define HW_BYTEORDER 4 /* int: byte order (4321 or 1234) */
5858 #define HW_PHYSMEM 5 /* int: total memory in bytes */
5959 #define HW_USERMEM 6 /* int: memory in bytes not wired */
6060 #define HW_PAGESIZE 7 /* int: software page size */
6161 #define HW_DISKNAMES 8 /* strings: disk drive names */
6262 #define HW_DISKSTATS 9 /* struct: diskstats[] */
6363 #define HW_FLOATINGPT 10 /* int: has HW floating point? */
6464 #define HW_MACHINE_ARCH 11 /* string: machine architecture */
6565 #define HW_REALMEM 12 /* int: 'real' memory */
6666 -------------------------------------------------------------------------------
6767```
6868
6969### Example: Number of CPUs
7070
7171Armed with this information, we can now find out the number of CPUs:
7272
7373``` pascal
7474{$mode objfpc}
7575...
7676
7777Uses
7878 unix,
7979 sysctl, // fpSysCtl
8080 sysutils; // RaiseLastOSError
8181...
8282
8383function NumberOfCPU: Integer;
8484
8585var
8686 mib: array[0..1] of Integer;
8787 status : Integer;
8888 len : size_t;
8989
9090begin
9191 mib[0] := CTL_HW;
9292 mib[1] := HW_NCPU;
9393
9494 len := SizeOf(Result);
9595 status := fpSysCtl(PChar(@mib), Length(mib), @Result, @len, Nil, 0);
9696 if status <> 0 then RaiseLastOSError;
9797end;
9898```
9999
100100The above code returns the number of CPUs in an integer; you need to check the C header file to determine what is being returned (an integer, int64, string, struct/record).
101101
102102There is a possible issue with the above because if you have, for example, a multi-core CPU. One guess. Yes, it will return the total number of cores and, if your microprocessor supports hyperthreading, the number of cores plus the number of hardware threads -- not the number of discrete CPUs. This may not matter depending on your use case, but you should be aware of it.
103103
104104### Example: CPU model
105105
106106Before we move on to look at a second function 'FPsysctlbyname', let's look at retrieving a string with FPsysctl() which is a little different than retrieving an integer.
107107
108108``` pascal
109109{$mode objfpc}
110110...
111111
112112Uses
113113 unix,
114114 sysctl, // fpSysCtl
115115 sysutils; // RaiseLastOSError
116116...
117117
118118function HWmodel : AnsiString;
119119
120120var
121121 mib : array[0..1] of Integer;
122122 status : Integer;
123123 len : size_t;
124124 p : PChar;
125125
126126begin
127127 mib[0] := CTL_HW;
128128 mib[1] := HW_MODEL;
129129
130130 status := fpSysCtl(PChar(@mib), Length(mib), Nil, @len, Nil, 0);
131131 if status <> 0 then RaiseLastOSError;
132132
133133 GetMem(p, len);
134134
135135 try
136136 status := fpSysCtl(PChar(@mib), Length(mib), p, @len, Nil, 0);
137137 if status <> 0 then RaiseLastOSError;
138138 Result := p;
139139 finally
140140 FreeMem(p);
141141 end;
142142end;
143143```
144144
145145Notice that this time we needed two calls to FPsysctl() - one to find out the size of the buffer required to hold the string value to be returned, and the second to actually retrieve the string value into that buffer. On my FreeBSD Apple Mac mini computer this returns the string "Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz".
146146
147147### Example: Clock info
148148
149149No, we're not quite ready to move on yet... we still need to see how to retrieve information in a C structure (Pascal record).
150150
151151``` pascal
152152{$mode objfpc}
153153...
154154
155155Uses
156156 unix,
157157 sysctl, // fpSysCtl
158158 sysutils; // RaiseLastOSError
159159...
160160
161161function GetClockInfo: String;
162162
163163type
164164 clockinfo = record
165165 hz : Integer; // clock frequency
166166 tick : Integer; // ms per Hz tick
167167 spare : Integer; // unused
168168 stathz : Integer; // statistics clock frequency
169169 profhz : Integer; // profiling clock frequency
170170 end;
171171
172172var
173173 mib : array[0..1] of Integer;
174174 status : Integer;
175175 len : size_t;
176176 clock : clockinfo;
177177begin
178178 mib[0] := CTL_KERN;
179179 mib[1] := KERN_CLOCKRATE;
180180 clock := Default(clockinfo);
181181
182182 FillChar(clock, sizeof(clock), 0);
183183 len := sizeof(clock);
184184
185185 status := fpSysCtl(@mib, Length(mib), @clock, @len, Nil, 0);
186186 if status <> 0 then RaiseLastOSError;
187187
188188 Result := 'Clock freq: ' + IntToStr(clock.hz) + 'Hz' + LineEnding
189189 + 'Ms per Hz tick: ' + IntToStr(clock.tick) + LineEnding
190190 + 'Profiling clock freq: ' + IntToStr(clock.profhz) + ' Hz'
191191 + LineEnding;
192192end;
193193```
194194
195195On my computer this returned:
196196
197-```
197+```text
198198Clock freq: 100Hz
199199Ms per Hz tick: 10000
200200Profiling clock freq: 8128 Hz
201201```
202202
203203The clockinfo record was pretty easily translated into a Pascal record from the C structure found in the system time.h file:
204204
205205``` c
206206struct clockinfo {
207207 int hz; /* clock frequency */
208208 int tick; /* micro-seconds per hz tick */
209209 int spare;
210210 int stathz; /* statistics clock frequency */
211211 int profhz; /* profiling clock frequency */
212212};
213213```
214214
215215### Example: Real executable path
216216
217217First some background. Many Pascal programmers are in love with the `ExtractFilePath(ParamStr(0))` function to find the path of an executable. **Just don't do it!** Paramstr(0) must only be used on DOS and Windows (and maybe OS/2). On all other platforms the results are unpredictable and it will not work as you expect (in the best case it will never work; in the worst case it will only fail under certain circumstances, resulting in hard-to-find bugs). For the gory details, see [this thread's posts by Jonas](https://lists.freepascal.org/pipermail/fpc-pascal/2009-February/020211.html).
218218
219219Instead, sysctl to the rescue.
220220
221221``` pascal
222222{$mode objfpc}
223223
224224Uses
225225 unix,
226226 sysctl, // fpSysCtl
227227 sysutils; // RaiseLastOSError
228228
229229function realPath: string;
230230
231231var
232232 mib: array[0..3] of Integer;
233233 status : Integer;
234234 len : size_t;
235235
236236begin
237237 mib[0] := CTL_KERN;
238238 mib[1] := KERN_PROC;
239239 mib[2] := KERN_PROC_PATHNAME;
240240 mib[3] := -1;
241241
242242 len := SizeOf(Result);
243243 status := fpSysCtl(PChar(@mib), Length(mib), @Result, @len, Nil, 0);
244244 if status <> 0 then RaiseLastOSError;
245245end;
246246
247247begin
248248 WriteLn(realPath);
249249end.
250250```
251251
252252Finally, yes, it is time to move on to the FPsysctlbyname() function.
253253
254254## FPsysctlbyname
255255
256256The FPsysctlbyname function is defined in '/usr/local/share/fpcsrc/rtl/bsd/sysctl.pp' as:
257257
258258``` pascal
259259function FPsysctlbyname (Name: pchar; oldp:pointer;oldlenp:psize_t; newp:pointer;newlen:size_t):cint; cdecl; external name 'sysctlbyname';
260260```
261261
262262There. Not really very different from the FPsysctl() function. Is that a sigh of relief?
263263
264264### Example: Number of CPU cores
265265
266266So let's look at finally retrieving the number of CPU cores.
267267
268268``` pascal
269269{$mode objfpc}
270270...
271271
272272Uses
273273 unix,
274274 sysctl, // fpSysCtlByName
275275 sysutils; // RaiseLastOSError
276276...
277277
278278function NumberOfCPU: Integer;
279279
280280var
281281 status : Integer;
282282 len : size_t;
283283
284284begin
285285 len := SizeOf(Result);
286286
287287 status := fpSysCtlByName('hw.ncpu', @Result, @len, Nil, 0);
288288 if status <> 0 then RaiseLastOSError;
289289end;
290290```
291291
292292On my FreeBSD computer this returns the number of cores: 6. Where do you find the special incantation 'hw.ncpu' ? In the manual pages for the sysctl command (*man 3 sysctl* \[Library functions section\] and *man 8 sysctl* \[System manager's section\]). Here's a brief excerpt of the hardware selectors:
293293
294-```
294+```text
295295 Parameters that are byte counts are 64 bit numbers. All other byte parameters are 32 bit numbers.
296296```
297297
298298``` pascal
299299 --------------------------------------------------------------------------------------------
300300 Descriptor Type Changeable Description
301301 --------------------------------------------------------------------------------------------
302302 hw.machine string no Machine class
303303 hw.model string no Machine model
304304 hw.ncpu integer no Number of cpus
305305 hw.byteorder integer no Byteorder (4321 or 1234)
306306 hw.physmem integer no Bytes of physical memory
307307 hw.usermem integer no Bytes of non-kernel memory
308308 hw.pagesize integer no Software page size
309309 hw.floatingpoint integer no Non-zero if hardware floating point
310310 hw.machine_arch string no Machine dependent architecture
311311 hw.realmem integer no Bytes of real memory
312312 --------------------------------------------------------------------------------------------
313313```
314314
315315If you prefer, you can also use the sysctl command line utility in a console or X11 xterm to list all the hw selectors with "sysctl hw.".
316316
317317### Example: CPU architecture
318318
319319Retrieving a string value, rather than an integer, is again reminiscent of FPsysctl(). Two calls to FPsysctlbyname() are needed: (1) to find out the size of the buffer to hold the string to be returned and (2) to return the string value into that buffer. Don't forget to allocate memory for the buffer before the second call!
320320
321321``` pascal
322322{$mode objfpc}
323323...
324324
325325Uses
326326 unix,
327327 sysctl, // fpSysCtlByName
328328 sysutils; // RaiseLastOSError
329329...
330330
331331function GetCPUarchitecture: AnsiString;
332332
333333var
334334 status : Integer;
335335 len : size_t;
336336 p : PChar;
337337begin
338338 status := fpSysCtlByName('hw.machine_arch', Nil, @len, Nil, 0);
339339 if status <> 0 then RaiseLastOSError;
340340
341341 GetMem(p, len);
342342
343343 try
344344 status := fpSysCtlByName('hw.machine_arch', p, @len, Nil, 0);
345345 if status <> 0 then RaiseLastOSError;
346346 Result := p;
347347 finally
348348 FreeMem(p);
349349 end;
350350end;
351351```
352352
353353On my FreeBSD computer, this returns the string: amd64.
354354
355355### Example: CPU clock speed
356356
357357``` pascal
358358{$mode objfpc}
359359...
360360
361361Uses
362362 unix,
363363 sysctl, // fpSysCtlByName
364364 sysutils; // RaiseLastOSError
365365...
366366
367367function GetCPUClockRate: integer;
368368
369369var
370370 status : Integer;
371371 len : size_t;
372372
373373begin
374374 len := SizeOf(Result);
375375 status := fpSysCtlByName('hw.clockrate', @Result, @len, Nil, 0);
376376 if status <> 0 then RaiseLastOSError;
377377end;
378378```
379379
380380For an Intel(R) Core(TM) i7-2620M CPU @ 2.70GHz, this returns 2.693 (GHz).
381381
382382## Last words
383383
384384For the optimised, performance fiends out there, the man page for these functions notes that the FPsysctl() function runs in about a third the time as the same request made via the FPsysctlbyname() function.
385385
386386It should be noted that many of the sysctl attributes are read-only, but there are also quite a few that can be changed. I've left changing a sysctl attribute as an exercise for you dear reader. It should be simple if you study the definitions of the function parameters, paying particular attention to newp and newlen and the manual page for sysctl (man 3 sysctl).
387387
388388## External links
389389
390390- [sysctlbyname improved](https://gitlab.com/alfix/sysctlbyname-improved) - A new sysctl internal object to convert a sysctl name to the corresponding OID and to improve sysctlbyname(3).
391391
392392---
393393*From the Free Pascal wiki, page "Accessing FreeBSD System Information", as it stood on 2021-03-09 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history