In the following article you can learn how to use the sysctl, sysctlbyname and sysctlnametomib functions to get system information (kernel values, hardware, networking, file system, machine specific and user related data) under the OSX and iOS systems.
sysctl
int sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen);
The sysctl allows you retrieves and set system information, the data returned from the sysctl() function consists of integers (int32, int64), strings(AnsiStrings) and structs (records). this function is defined in the Posix.SysSysctl (SysSysctlAPI.inc) unit
Note : The Posix.SysSysctl unit is a partial translation the sysctl.h file.
function sysctl(name: PInteger; namelen: cardinal; oldp: Pointer; oldlen: Psize_t; newp: Pointer; newlen: size_t): Integer; cdecl; external libc name _PU + 'sysctl';
name : This parameter receive a pointer to a Management Information Base (MIB) style value, which is only a array of integers. Each element of this array must be filled with the values related to the info to read or write.
The number of elements of this array, depends of the data to be obtained or modified, most of times only we need to pass and fill a mib with 2 elements (integer values). The first element indicates the level(location) of the info and the second element indicates the value to retrieve.
These are the possible values for the first element of the mib.
Name Value Description
CTL_DEBUG $00000005 Debugging
CTL_VFS $00000003 File system
CTL_HW $00000006 Generic CPU, I/O
CTL_KERN $00000001 High kernel limits
CTL_MACHDEP $00000007 Machine dependent
CTL_NET $00000004 Networking
CTL_USER $00000008 User-level
CTL_VM $00000002 Virtual memory
Note: All these values are already defined in the Posix.SysSysctl unit.
So if we want access to the kernel related values we must fill the mib like so
var
mib : array[0..1] of Integer;
...
...
mib[0] := CTL_KERN;
The second element value is related to the first level and the possible values are defined in the Posix.SysSysctl unit as well, for example to get the max processes supported by the system we must use the KERN_MAXPROC($00000006) value.
var
mib : array[0..1] of Integer;
...
...
mib[0] := CTL_KERN;
mib[1] := KERN_MAXPROC;
namelen : This parameter is the length of the mib structure.
oldp: Must be filled with a pointer to the buffer to receive. The info which can be a integer, int64, a AnsiString(MarshaledAString) or a record.
oldlen: Indicates the size of the oldp parameter.
newp: This parameter must be filled with a pointer to the buffer to with the info to set up, when you don’t modify the data you must pass a nil value.
newlen: Indicates the size of the newp parameter.
So with all the above info now you can write a function to retrieve the max processes supported, check this sample code which retrieves a integer value.
function MaxProcesses : Integer;
var
mib : array[0..1] of Integer;
res : Integer;
len : size_t;
begin
mib[0] := CTL_KERN;
mib[1] := KERN_MAXPROC;
len := sizeof(Result);
res:=sysctl(@mib, Length(mib), @Result, @len, nil, 0);
if res<>0 then
RaiseLastOSError;
end;
To get a Int64 value the code is very similar, check this sample which get the size of the memory installed on the system using the CTL_HW level and the HW_MEMSIZE value.
function MemSize : Int64;
var
mib : array[0..1] of Integer;
res : Integer;
len : size_t;
begin
mib[0] := CTL_HW;
mib[1] := HW_MEMSIZE;
len := sizeof(Result);
res := sysctl(@mib, Length(mib), @Result, @len, nil, 0);
if res<>0 then
RaiseLastOSError;
end;
If you want retrieve a string value, you must get the length of the value before to allocate the buffer, to do this you must pass a nil value in the oldp parameter like so.
sysctl(@mib, Length(mib), nil, @len, nil, 0)
The next code shows how to get a string(ascii) type using the sysctl function.
function KernelVersion : AnsiString;
var
mib : array[0..1] of Integer;
res : Integer;
len : size_t;
p : MarshaledAString;//in XE2 you can use the PAnsiChar type
begin
mib[0] := CTL_KERN;
mib[1] := KERN_VERSION;
//get the length of the buffer
res := sysctl(@mib, Length(mib), nil, @len, nil, 0);
if res<>0 then
RaiseLastOSError;
//allocates the buffer
GetMem(p, len);
try
res := sysctl(@mib, Length(mib), p, @len, nil, 0);
if res<>0 then
RaiseLastOSError;
Result:=p;
finally
FreeMem(p);
end;
end;
Finally we can use the sysctl function to retrieve complex structures(records) passing a pointer to the record to hold the data.
Try this sample which get the clock rate values from the kernel.
procedure GetClockInfo;
type
clockinfo = record
hz : Integer;
tick : Integer;
tickadj : Integer;
stathz : Integer;
profhz : Integer;
end;
(*
struct clockinfo {
int hz; /* clock frequency */
int tick; /* micro-seconds per hz tick */
int tickadj;/* clock skew rate for adjtime() */
int stathz; /* statistics clock frequency */
int profhz; /* profiling clock frequency */
};
*)
var
mib : array[0..1] of Integer;
res : Integer;
len : size_t;
clock : clockinfo;
begin
FillChar(clock, sizeof(clock), 0);
mib[0] := CTL_KERN;
mib[1] := KERN_CLOCKRATE;
len := sizeof(clock);
res:=sysctl(@mib, Length(mib), @clock, @len, nil, 0);
if res<>0 then
RaiseLastOSError;
Writeln(Format('clock frequency %d',[clock.hz]));
Writeln(Format('micro-seconds per hz tick %d',[clock.tick]));
Writeln(Format('clock skew rate for adjtime %d',[clock.tickadj]));
Writeln(Format('statistics clock frequency %d',[clock.stathz]));
Writeln(Format('profiling clock frequency %d',[clock.profhz]));
end;
sysctlbyname
int sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen);
The sysctlbyname works in the same way which the sysctl, the only difference is which the values are accessed using a alias string. because that you don’t need pass a mib structure and length to this function.
function sysctlbyname(Name: MarshaledAString; oldp: Pointer; oldlen: Psize_t; newp: Pointer; newlen: size_t): Integer; cdecl; external libc name _PU + 'sysctlbyname';
name: this parameter is the alias for the info to access and is composed by the level splus the string representation of the value to get, because that you don’t need pass a mib structure.
These are the possible values for the first level element
Name string
CTL_DEBUG debug
CTL_VFS vfs
CTL_HW hw
CTL_KERN kern
CTL_MACHDEP machdep
CTL_NET net
CTL_USER user
CTL_VM vm
This is a sample list of some of the values which you can use in the name parameter of the sysctlbyname function.
Name Type
kern.ostype string
kern.osrelease string
kern.osrevision integer
kern.version string
kern.maxvnodes integer
kern.maxproc integer
kern.maxfiles integer
kern.argmax integer
kern.securelevel integer
kern.hostname string
kern.hostid integer
kern.clockrate struct
kern.posix1version integer
kern.ngroups integer
kern.job_control integer
kern.saved_ids integer
kern.link_max integer
kern.max_canon integer
kern.max_input integer
kern.name_max integer
kern.path_max integer
kern.pipe_buf integer
kern.chown_restricted integer
kern.no_trunc integer
kern.vdisable integer
kern.boottime struct
vm.loadavg struct
vm.swapusage struct
machdep.console_device dev_t
net.inet.ip.forwarding integer
net.inet.ip.redirect integer
net.inet.ip.ttl integer
net.inet.icmp.maskrepl integer
net.inet.udp.checksum integer
hw.machine string
hw.model string
hw.ncpu integer
hw.byteorder integer
hw.physmem integer
hw.usermem integer
hw.memsize integer
hw.pagesize integer
user.cs_path string
user.bc_base_max integer
user.bc_dim_max integer
user.bc_scale_max integer
user.bc_string_max integer
user.coll_weights_max integer
user.expr_nest_max integer
user.line_max integer
user.re_dup_max integer
user.posix2_version integer
user.posix2_c_bind integer
user.posix2_c_dev integer
user.posix2_char_term integer
user.posix2_fort_dev integer
user.posix2_fort_run integer
user.posix2_localedef integer
user.posix2_sw_dev integer
user.posix2_upe integer
Note : You can get a full list of the supported values running the sysctl -A command from a Terminal.
Finally this code shows how use the SysCtlByName function to retrieve the number of cpus installed.
function NumberOfCPU: Integer;
var
res : Integer;
len : size_t;
begin
len := SizeOf(Result);
res:=SysCtlByName('hw.ncpu', @Result, @len, nil, 0);
if res<>0 then
RaiseLastOSError;
end;
Note : The sysctl function runs in about a third the time as the same request made via the sysctlbyname, so when is possible uses sysctl instead.
sysctlnametomib
int sysctlnametomib(const char *name, int *mibp, size_t *sizep);
The sysctlnametomib function fill a mib structure using a alias a string. this function is intended for use by apps that want to repeatedly use the same variable.
function sysctlnametomib(name: MarshaledAString; mibp: PInteger; sizep: Psize_t): Integer; cdecl; external libc name _PU + 'sysctlnametomib';
name: ASCII representation of the value to retrieve.
mibp: pointer to the mib structure to fill.
sizep: pointer to the length of the mib structure to fill.
var
mib : array[0..1] of Integer;
res : Integer;
len : size_t;
begin
len := Length(mib);
sysctlnametomib('hw.physicalcpu', @mib, @len);
//now the mib structure is filled with the proper values to call the sysctl function.
Error Handling
All the above functions returns a 0 values when the execution was succefull, otherwise an error code is returned, this code can be obtained using the errno (Posix.Errno) or the GetLastError function.
The following errors may be reported (theses values and meanings are defined in the osx/ErrnoTypes.inc file)
[EFAULT] The buffer name, oldp, newp, or length pointer oldlenp contains an invalid address.
[EINVAL] The name array is less than two or greater than CTL_MAXNAME.
[EINVAL] A non-null newp is given and its specified length in newlen is too large or too
small.
[ENOMEM] The length pointed to by oldlenp is too short to hold the requested value.
[ENOMEM] The smaller of either the length pointed to by oldlenp or the estimated size of the
returned data exceeds the system limit on locked memory.
[ENOMEM] Locking the buffer oldp, or a portion of the buffer if the estimated size of the
data to be returned is smaller, would cause the process to exceed its per-process
locked memory limit.
[ENOTDIR] The name array specifies an intermediate rather than terminal name.
[EISDIR] The name array specifies a terminal name, but the actual name is not terminal.
[ENOENT] The name array specifies a value that is unknown.
[EPERM] An attempt is made to set a read-only value.
[EPERM] A process without appropriate privilege attempts to set a value.
This a full sample console app which summarizes this article.
{$APPTYPE CONSOLE}
uses
//System.Classes,
//System.Types,
//Posix.Errno,
Posix.SysTypes,
Posix.SysSysctl,
System.SysUtils;
//https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/sysctl.3.html
//https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/sysctl.8.html
function NumberOfCPU: Integer;
var
res : Integer;
len : size_t;
begin
len := SizeOf(Result);
res:=SysCtlByName('hw.ncpu', @Result, @len, nil, 0);
if res<>0 then
RaiseLastOSError;
end;
function MaxProcesses : Integer;
var
mib : array[0..1] of Integer;
res : Integer;
len : size_t;
begin
mib[0] := CTL_KERN;
mib[1] := KERN_MAXPROC;
len := sizeof(Result);
res:=sysctl(@mib, Length(mib), @Result, @len, nil, 0);
if res<>0 then
RaiseLastOSError;
end;
function MemSize : Int64;
var
mib : array[0..1] of Integer;
res : Integer;
len : size_t;
begin
mib[0] := CTL_HW;
mib[1] := HW_MEMSIZE;
len := sizeof(Result);
res := sysctl(@mib, Length(mib), @Result, @len, nil, 0);
if res<>0 then
RaiseLastOSError;
end;
function KernelVersion : AnsiString;
var
mib : array[0..1] of Integer;
res : Integer;
len : size_t;
p : MarshaledAString;//in XE2 use PAnsiChar
begin
mib[0] := CTL_KERN;
mib[1] := KERN_VERSION;
res := sysctl(@mib, Length(mib), nil, @len, nil, 0);
if res<>0 then
RaiseLastOSError;
GetMem(p, len);
try
res := sysctl(@mib, Length(mib), p, @len, nil, 0);
if res<>0 then
RaiseLastOSError;
Result:=p;
finally
FreeMem(p);
end;
end;
procedure GetClockInfo;
type
clockinfo = record
hz : Integer;
tick : Integer;
tickadj : Integer;
stathz : Integer;
profhz : Integer;
end;
(*
struct clockinfo {
int hz; /* clock frequency */
int tick; /* micro-seconds per hz tick */
int tickadj;/* clock skew rate for adjtime() */
int stathz; /* statistics clock frequency */
int profhz; /* profiling clock frequency */
};
*)
var
mib : array[0..1] of Integer;
res : Integer;
len : size_t;
clock : clockinfo;
begin
FillChar(clock, sizeof(clock), 0);
mib[0] := CTL_KERN;
mib[1] := KERN_CLOCKRATE;
len := sizeof(clock);
res:=sysctl(@mib, Length(mib), @clock, @len, nil, 0);
if res<>0 then
RaiseLastOSError;
Writeln(Format('clock frequency %d',[clock.hz]));
Writeln(Format('micro-seconds per hz tick %d',[clock.tick]));
Writeln(Format('clock skew rate for adjtime %d',[clock.tickadj]));
Writeln(Format('statistics clock frequency %d',[clock.stathz]));
Writeln(Format('profiling clock frequency %d',[clock.profhz]));
end;
begin
try
Writeln(Format('max processes %d',[MaxProcesses]));
Writeln(Format('number of cpus %d',[NumberOfCPU]));
Writeln(Format('physical ram size %s',[FormatFloat('#,', MemSize)]));
Writeln(Format('Kernel Version %s',[KernelVersion]));
GetClockInfo;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
-33.636934
-70.679350