Services
A service is a program that runs in the background, started at boot or by hand, such as
rshd, the remote shell’s daemon. mcos manages them with rc,
in OpenRC’s shape, with FreeBSD’s way of turning them on.
The pieces
/etc/rcis the boot script. The kernel runs it withshbefore the console’s shell starts (see Boot). It ends withrc boot./etc/rc.d/<name>defines a service, a Lua script in the system’s own language (as/etc/init.dscripts are in the shell’s; the shell here has noif)./etc/rc.confsays which start at boot, a line each:name_enable=YES.
A service
A service’s script sets a few settings, as OpenRC’s do:
| Setting | Meaning |
|---|---|
command |
The program to run. |
command_args |
Its arguments: a list, or a string split on spaces. |
description |
What it is. |
output_log |
Where its output and errors go. /dev/null when not given. |
The script runs in its own environment, with name set to the service’s name. This is all
/etc/rc.d/rshd is:
description = "remote shell daemon"
command = "/bin/rshd"
From the settings, rc gives it:
- start runs the command as a daemon: in a session of its own (so no terminal’s Ctrl+C
reaches it), with its input from
/dev/null, and its process id in/run/<name>.pid; - stop sends SIGTERM, then SIGKILL if it hasn’t ended in five seconds;
- restart;
- status, which says whether it is running. The status is 0 when running and 3 when not (LSB’s “program is not running”).
A script may define a function named for one of those actions, start, stop, and so on, and it is
used instead of the default. Any other function it defines is an action too: rc name reload runs a
reload().
Using rc
$ rc # every service: enabled or not, running or not
$ rc rshd start
$ rc rshd status
rshd is running (pid N)
$ rc rshd enable # starts at boot: writes rshd_enable=YES to /etc/rc.conf
$ rc rshd disable
rc boot starts every service whose name_enable is YES in /etc/rc.conf, in the file’s order. It
is run by /etc/rc, and you can run it by hand.
/run is a filesystem in memory, made by /etc/rc at every boot, so a pid file never outlives
the computer. A service started by hand stops with the computer too; one in rc.conf starts
again at the next boot.
Writing your own
The tutorial Your own service has two complete ones, each with its program,
its /etc/rc.d definition and every command. In short: put a Lua script in /etc/rc.d named after
the service, with a command:
-- /etc/rc.d/ticker: the ticker service, a line every 2 seconds, its output and errors in /tmp.
description = "writes the time to /home/ticker.log"
command = "/bin/lua"
command_args = {"/home/ticker.lua", "2"}
output_log = "/tmp/ticker.out"
And the program, a daemon that loops forever, sleeps between turns, and ends cleanly when stopped:
-- ticker: a service that wakes every few seconds, forever, and writes the world's time to
-- /home/ticker.log; when rc stops it (SIGTERM), it finishes the line it's on, says so, and ends.
-- Its argument is how many seconds it sleeps between lines (5 when not given).
local sys = require("sys")
local LOG = "/home/ticker.log"
local DEFAULT_SECONDS = 5 -- how long it sleeps between lines, unless its argument says
local seconds = tonumber(arg[1]) or DEFAULT_SECONDS
-- Appends a line to the log. Opened and closed each time: a file on a disk shows what's written
-- only once it's closed, so you can read the log while it runs.
local function log(text)
local file = assert(io.open(LOG, "a"))
file:write(os.date("%c"), " ", text, "\n")
file:close()
end
-- rc's stop sends SIGTERM. The handler only notes it; the loop sees the note and ends. A sleep
-- the signal came in during returns early (EINTR), so it ends at once, not a sleep later.
local stopping = false
sys.signal("SIGTERM", function()
stopping = true
end)
log("started, a line every " .. seconds .. " seconds")
local beats = 0
while not stopping do -- forever, until stopped
beats = beats + 1
log("tick " .. beats)
sys.sleep(seconds) -- waits without using the CPU: the computer draws its waiting power
end
log("stopped after " .. beats .. " ticks")
Then rc ticker start runs it, rc ticker stop stops it, and rc ticker enable starts it at boot.
- A loop forever is right for a daemon. mcos runs processes in turns, so it never freezes the computer.
- Sleep, or wait on something (
sys.sleep, a device’s file,sys.poll, a socket): a waiting process uses no CPU, so the computer draws its waiting power. rc stopsendsSIGTERM. With no handler it ends the process wherever it is. With a handler (sys.signal, in The sys library) the handler runs and a waiting call returns early withEINTR, so the loop can finish and clean up. Five seconds later,SIGKILLends it regardless.
The kernel stays init: it runs the console’s shell and starts it again when it ends. A user-space PID 1 would stay in memory for nothing, so there is none.