Lunatik

Lua in the kernel

Lunatik 5.1 is based on Lua 5.5 adapted to run in the kernel.

Floating-point numbers

Lunatik does not support floating-point arithmetic, thus it does not support __div nor __pow metamethods and the type number has only the subtype integer, a 64-bit integer. The / operator is floor division, the same as //, so 7 / 2 is 3; ^ is not an operator; and a numeral with a decimal point or an exponent, 1.5 or 1e3, does not parse. A fraction takes fixed point.

Lua API

Lunatik does not support the os library, floating-point arithmetic (__div, __pow), or debug.debug. The math library keeps abs, max, min, tointeger, ult, maxinteger and mininteger. The rest is absent, math.random (use linux.random), math.floor and math.type (use type(x) == "number") among them. string.format has no %a, %e, %f or %g conversion, string.pack and string.unpack have no f or d option, and their n is a 64-bit integer.

The io library is supported with the following limitations: there are no default streams (io.stdin, io.stdout, io.stderr), no default input/output (io.read, io.write, io.input, io.output), no process pipes (io.popen), no temporary files (io.tmpfile), and no buffering control (file:setvbuf). The available functions are io.open, io.lines, io.type, and the file handle methods read, write, lines, flush, seek, and close. On failure no errno reaches the script: io.open returns nil, "<file>: (no extra info)" and 0, and a function that raises, such as io.lines, says I/O error. A file loadfile, dofile or require opens and cannot read fails with the errno’s name: EINVAL for a directory. A file loadfile or dofile cannot open fails with cannot open <file>: and the errno’s name, ENOENT for a missing one.

Lunatik modifies the following identifiers: * _VERSION: is defined as "Lua 5.5-kernel". * collectgarbage("count"): returns the total memory in use by Lua in bytes, instead of Kbytes. * package.path: is defined as "/lib/modules/lua/?.lua;/lib/modules/lua/?/init.lua". * require: finds the opener luaopen_<name> a loaded Lunatik module exports, and then a Lua file on package.path, so a file named like a binding does not shadow it; it cannot load kernel modules. The CLI loads every module the build produced, and CONFIG_LUNATIK_<NAME> := n in the Makefile leaves one out; requiring a C module that is not loaded raises an error naming luaopen_<name> not found in kernel symbol table. * print: writes to the kernel log (dmesg -w or journalctl -k -f), not to a terminal, and warn writes there too, as Lua warning: <message> at KERN_ERR. The REPL and -e bring back only what a chunk returns.

Lunatik adds the global _LUNATIK_VERSION, the Lunatik release.

A coroutine’s Lua stack holds at most 200 slots (LUAI_MAXSTACK), which bounds recursion and the values table.unpack and a vararg carry: past it, recursion raises stack overflow and table.unpack raises too many results to unpack.

A call that crosses C, through pcall, a metamethod, a callback a binding makes, a coroutine resumed inside another or a runtime a script creates or resumes, nests on the kernel stack instead, which Lunatik bounds in bytes (LUAI_MAXCCALLS): past five eighths of it (THREAD_SIZE), counted from where the kernel entered Lua, at a hook, a thread’s start, the CLI’s write or the close that runs a runtime’s finalizers, such a call raises C stack overflow, as a chunk whose syntax nests that deep does. A runtime a script creates or resumes counts from where its creator or resumer was entered, so a script that creates itself runs out too. Under xpcall, a message handler called past the bound raises in turn, and the call ends with error in error handling. On the task stack the call also raises once a quarter of the stack is all that is left, so a hook entered deep, as a kprobe in the network stack is, or a runtime another script closes, gets what remains; a hook on an interrupt stack is bounded from its entry alone. Loading a binary chunk and string.dump recurse once per function the chunk nests, and nothing counts that against the bound, so a chunk whose functions nest more than ten deep is out of contract: loaded or dumped where the stack is already deep, it can run the kernel stack into its guard page instead of raising. lunatic compiles such a chunk all the same.

A string pattern nests at most 32 levels deep: the match takes one, each capture two, and each position capture () and each item with ?, *, + or - that matches one more. Past it, string.find, string.match, string.gmatch and string.gsub raise pattern too complex.

Reserved names

Scripts and the modules they require share one root, /lib/modules/lua/, whose top-level names are Lunatik’s: the top-level name of every module this reference documents, a binding such as socket or a Lua library such as util; lunatik, linux, examples and tests, which hold the runtime’s own scripts, the linux.* constants, the examples and the test suites; and light, the key lighten requires, which the user supplies. A binding’s name followed by a hyphen, socket-<x>, is the binding’s as well: require loads luaopen_socket for it. A product’s scripts live in a directory of their own, /lib/modules/lua/<product>/, and run as <product>/<script>.

Softirq and hardirq runtimes

In a runtime created in softirq or hardirq context, io is nil, require("io") raises 'io': process-context class in interrupt-context runtime, and the lunatik table holds only cpu() and _ENV, so lunatik.runtime and lunatik.percpu are absent. A module is required at the top level of the script, since opening a Lua library’s file sleeps and loading a binding takes a reference on its kernel module that an atomic allocation failing after it would leak: once the runtime is armed, a require of a binding or a Lua library the body did not load, package.loadlib, package.searchpath and dofile raise not allowed once the runtime is armed, and loadfile returns it as its error.

Edit this page on GitHub