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.