Vivian Voss

sysctl Explains the Kernel

technical beauty scope freebsd sysctl kernel

Most of us have a sysctl.conf somewhere with a line in it that came from a blog post. net.core.somaxconn = 4096, perhaps, or vm.swappiness = 10, pasted in during a long evening because somebody on the internet was very confident, and left there since because nothing went wrong. Whether it ever did anything useful is a question for another long evening.

This morning I asked the machine that serves this blog about one of its own settings.

% sysctl -d net.inet.tcp.blackhole
net.inet.tcp.blackhole: Do not send RST on segments to closed ports

One line, from the kernel itself, on the machine itself. Then I asked it about all of them, and 15,065 of the 15,416 entries in its tree came back with a sentence of their own.

The same two characters typed on a Linux machine do something else. In procps, which supplies the sysctl command there, -d is documented as "Alias of -h", so the kernel's settings are answered with the help page. Two further switches, -o and -x, are listed with the note "Does nothing, exists for BSD compatibility", which is a courteous way of saying where the command came from.

One line in the declaration

The reason the FreeBSD kernel can explain itself sits in a macro. Every setting in the tree is declared in the C source with one of a family of SYSCTL_ macros, and here is the plainest of them, from sys/sys/sysctl.h in 15.1:

#define SYSCTL_INT(parent, nbr, name, access, ptr, val, descr)

The last argument is the sentence. It cannot be left out of the macro, though it can be left empty, and it does not live in a separate file, so a developer who adds a setting writes the explanation in the same line, in the same commit, and a reviewer reads both together. A typical declaration from the network stack:

SYSCTL_INT(_net_inet_tcp, OID_AUTO, blackhole, CTLFLAG_VNET | CTLFLAG_RW,
    &VNET_NAME(blackhole), 0,
    "Do not send RST on segments to closed ports");

When the code changes, the sentence is standing beside it, and when the setting is removed, the sentence goes with it. Documentation kept in another file ages on a schedule of its own, which is how a manual comes to describe a default that changed three releases ago.

The Linux kernel declares its settings in a structure called ctl_table, and in today's source it has eight fields: the name, a pointer to the data, its length, the file mode, a handler that turns the value into text, a poll hook and two spare arguments. A sentence has no field to go in. The explanations exist, and many of them are good, but they live in eleven files under Documentation/admin-guide/sysctl/ in the kernel's source tree and on docs.kernel.org, which is to say on a different shelf from the machine you are logged into.

Where the explanation of a setting lives FreeBSD 15.1 Linux declared with a SYSCTL_ macro SYSCTL_INT(_net_inet_tcp, OID_AUTO, blackhole, ..., 0, "Do not send RST on segments to closed ports"); the sentence is compiled into the kernel beside the variable it describes sysctl -d at the prompt on the machine, for the kernel that runs declared in struct ctl_table procname, data, maxlen, mode, proc_handler, poll, extra1, extra2 eight fields, none for a sentence Documentation/admin-guide/sysctl eleven files, and the documentation website sysctl -d in procps "Alias of -h": the help page same line, same commit, same kernel a different file on a different shelf sys/sys/sysctl.h and sys/netinet/tcp_input.c, releng/15.1 · include/linux/sysctl.h · procps-ng sysctl(8)

Six years of explanations nobody could read

The sysctl interface itself came from Mike Karels at Berkeley Software Design and first appeared in 4.4BSD. Poul-Henning Kamp rebuilt it for FreeBSD over the autumn of 1995, and on 28 October of that year he committed the macros that every setting has been declared with since, the descr argument already in place.

There was a catch, and it lasted six years. The 1995 structure that held each setting in the running kernel had no field for the description, and the macro, which took the sentence as its last argument, simply left it out when it built the structure. Developers wrote their explanations dutifully for six years. The kernel kept none of them, and they sat in the source tree where only somebody reading the code could find them.

On 16 December 2001 Luigi Rizzo, at the suggestion of Orion Hodson, gave the structure the missing field and the command its -d. His commit message closed with two notes that are worth quoting for their tone. "Note to developers: have a look at your code, there are a number of variables which do not have a description." And: "do we want this in 4.5 ? It is a very small change and very useful for documentation purposes." It shipped with FreeBSD 5.0 in January 2003 and reached the 4.x branch with 4.9 that October.

Thirty-one years of a sentence beside every setting written, not kept 1993 2003 2013 2023 1993 · 4.4BSD sysctl, Mike Karels, BSDI 28 Oct 1995 · Kamp macros with a sentence argument 16 Dec 2001 · Rizzo sysctl -d, suggested by Hodson Jan 2003 · FreeBSD 5.0 ships it Jan 2014 · FreeBSD 10.0 somaxconn renamed, old name kept Oct 2026 · 15.1 15,065 of 15,416 described sysctl(8) history · commits b396cd832ccd and 6105f815658a · releng/10.0 uipc_socket.c · own count, 9 October 2026

The count

Rizzo's first note invited a measurement, so here it is. I walked the whole tree on FreeBSD 15.1-RELEASE-p3, in a jail kept for measurements on the machine that serves this blog, and asked every entry for its description.

branch      entries   described   without
vm            8,652       8,651         1
dev           2,098       2,096         2
kstat         1,288       1,246        42
kern            877         874         3
vfs             756         746        10
net             587         561        26
hw              574         528        46
debug           324         134       190
security        126         124         2
machdep          63          63         0
p1003_1b         26           2        24
user             22          20         2
compat           19          16         3
sys               4           4         0

total        15,416      15,065       351

That is 97.7 per cent of the tree, branches and values together. (I ran the loop a second time, since a column that tidy usually means the loop is broken. It was not.) Leave out the two branches whose purpose is internal, debug with its developer counters and kstat with the statistics that OpenZFS brings along from its own code base, and the figure is 13,685 of 13,804, or 99.1 per cent.

How much of the tree explains itself 97.7 % 15,065 of 15,416 351 without a description 190 debug counters 46 hypervisor entries under hw.vmm 42 ZFS statistics under kstat 24 POSIX realtime constants (of 26) 49 everything else outside debug and kstat: 13,685 of 13,804, or 99.1 per cent FreeBSD 15.1-RELEASE-p3, one machine, sysctl -aN and sysctl -dn per entry, 9 October 2026

The gaps are instructive in their own right. Most of the debug entries without a sentence are counters inside the soft-updates code, which nobody outside the filesystem has reason to touch. The hw gaps sit almost entirely under hw.vmm, the hypervisor. And the branch with the worst record in the whole tree is p1003_1b, the POSIX realtime constants, where 24 of 26 entries have no description at all, which is the one place in the kernel where the standard was expected to speak for itself.

What else the tree knows

A description is the visible part. Underneath it every entry carries a type, and sysctl -t reports twelve of them across the tree: 3,248 unsigned 64-bit values, 2,473 integers, 1,784 strings, 129 opaque structures and so on down. The command uses the type to print a value properly, so a structure arrives as a structure:

% sysctl vm.loadavg kern.boottime
vm.loadavg: { 2.52 2.39 2.33 }
kern.boottime: { sec = 1789366576, usec = 8077 } Mon Sep 14 06:16:16 2026

The tree also knows what may be changed and when. sysctl -W lists the 2,273 entries that can be written on the running system, and sysctl -T the 1,316 that the loader can set at boot. The two lists overlap: kern.maxfiles is on both, whilst kern.hz, the clock rate, is on the second alone and therefore belongs in loader.conf, a question the kernel answers before anybody puts the setting in the wrong file. Reading the whole tree with every value took 0.47 seconds, median of three runs; reading every description took 0.03.

A jail sees the same tree and may not change it. From inside the jail I measured in, sysctl net.inet.tcp.blackhole=1 and sysctl kern.maxfiles=100000 were both refused with "Operation not permitted", whilst every description remained readable. On Linux a container that should not tune the host is kept away from /proc/sys by mounting it read-only, which is a separate arrangement made by the container runtime.

Old names still answer

The last thing worth measuring is whether a setting from an old tuning guide still works. In FreeBSD 10.0, in January 2014, kern.ipc.somaxconn, the length of the queue of connections waiting to be accepted, was renamed to the more accurate kern.ipc.soacceptqueue. The old name still answers on 15.1, and its description quietly owns up to its age:

% sysctl -d kern.ipc.somaxconn
kern.ipc.somaxconn: Maximum listen socket pending connection accept queue size (compat)

The ZFS cache limit went through the same process when FreeBSD moved to OpenZFS, and vfs.zfs.arc_max still sits beside vfs.zfs.arc.max, labelled "(LEGACY)". kern.maxfiles, kern.ipc.maxsockbuf, net.inet.tcp.sendspace and net.inet.tcp.blackhole, the names a tuning article of 2005 would have used, are all present and all described. A sysctl.conf written twenty years ago still loads at boot, where /etc/rc.d/sysctl runs sysctl -i and quietly skips any name the kernel has dropped since, and a name that lives on as an alias says so under sysctl -d.

The sheet

Efficiency. /sbin/sysctl is 24,992 bytes. The command is 1,124 lines of C without comments, the kernel side in kern_sysctl.c 2,271, and the header that declares the macros 954, all on releng/15.1. The whole tree of 15,416 entries reads in 0.47 seconds with values and 0.03 seconds with descriptions. No runtime dependencies beyond libc.

Security. Of the four questions: sysctl runs as whoever calls it, nearly all of the tree can be read without privilege, and writing needs root; it takes no input from the network; its parser handles a dotted name and a typed value; and the kernel checks privilege for every write, refuses writes from a jail, and refuses some writes outright once the secure level is raised. Of the 725 FreeBSD security advisories published so far, none carries sysctl in its title. The one entry NVD returns for FreeBSD and sysctl, CVE-2018-17156, is a buffer error in the ICMP code that a non-default value of net.inet.icmp.quotelen could reach, which places the fault in the code that the setting configures.

Longevity. The interface since 4.4BSD; the FreeBSD form with its macros since 28 October 1995; descriptions readable since 16 December 2001, released in 5.0 in January 2003. Maintained in the base system. A sysctl command exists on FreeBSD, macOS, OpenBSD and Linux; the description is FreeBSD's.

Stability. Renamed settings keep their old names and say so in their description, as kern.ipc.somaxconn has for twelve years, and the boot script loads sysctl.conf with -i, so a name that has disappeared is skipped without stopping the rest. The macro that declares a setting has had its description argument for thirty-one years. The configuration file is /etc/sysctl.conf, one setting per line, and a working minimum is one line.

Measured on 9 October 2026 on FreeBSD 15.1-RELEASE-p3, Intel Core i5-13500, in a jail kept for measurements, with sysctl from the base system: sysctl -aN for the count, sysctl -dn per entry for the descriptions, sysctl -at for the types, sysctl -aWN and sysctl -aTN for writable entries and tunables, /usr/bin/time -p sysctl -a and sysctl -ad, three runs each, median.

The limit

Four limits, and the first is the one a Linux administrator will raise.

A description is one line, and a line is not a manual. The kernel documentation on the Linux side often runs to a paragraph per setting, with the history and the trade-offs, and for a setting like vm.swappiness that paragraph is worth more than any sentence. FreeBSD keeps the longer form in tuning(7), 320 lines of it, and in the Handbook. What the one-liner buys is the answer at the prompt, on the machine, for the exact kernel that is running.

351 entries say nothing. Their declarations were given no sentence. Most of them are developer counters, and the realtime constants of POSIX are the embarrassing exception. Rizzo's note of 2001 has not been fully answered, and nobody pretends otherwise.

The Linux side was not counted here. I measured one FreeBSD machine and read the Linux source and its manuals; how many of the entries under /proc/sys on a given distribution are covered by the kernel's documentation is a measurement for a Linux machine.

Measured on one machine and one release. The tree differs with the hardware and the loaded modules, and the counts above are this machine's. The share of described entries is the figure that travels.

On FreeBSD the explanation was written into the declaration in 1995 and has been readable at the prompt since 2003, and the kernel will tell anyone who types the two extra characters what a setting is for before they copy it from a blog post.

Every FreeBSD kernel setting is declared with a macro whose last argument is the sentence that explains it, so the explanation sits beside the variable and ships in the kernel. On FreeBSD 15.1, 15,065 of 15,416 settings answer sysctl -d; renamed ones keep their old names, and a jail may read the tree whilst every write from it is refused.