|
|
|
@ -5,18 +5,18 @@ bootfs = bootfs or computer.getBootAddress()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--store all functions we need in locals so userspace mischief can't replace them
|
|
|
|
|
--suffix 'K' (for kernel) on any functions userspace will also have a version of to ensure kernelspace
|
|
|
|
|
--prefix 'k' (for kernel) on any functions userspace will also have a version of to ensure kernelspace
|
|
|
|
|
--function usage is intentional (ie prevent accidentally using the kernel's `load` in a userspace `loadfile`)
|
|
|
|
|
local envBase = _G --used for creating process environments
|
|
|
|
|
local assert, checkArg, error, loadK, ipairs, next, pairs, rawequal, rawset, setmetatable, tostring, type = assert, checkArg, error, load, ipairs, next, pairs, rawequal, rawset, setmetatable, tostring, type
|
|
|
|
|
local assert, checkArg, error, kload, ipairs, next, pairs, rawequal, rawset, setmetatable, tostring, type = assert, checkArg, error, load, ipairs, next, pairs, rawequal, rawset, setmetatable, tostring, type
|
|
|
|
|
local invoke = component.invoke
|
|
|
|
|
local pullSignalK, shutdown, uptime = computer.pullSignal, computer.shutdown, computer.uptime
|
|
|
|
|
local co_create, co_status, co_resumeK, co_yieldK = coroutine.create, coroutine.status, coroutine.resume, coroutine.yield
|
|
|
|
|
local kpullSignal, shutdown, uptime = computer.pullSignal, computer.shutdown, computer.uptime
|
|
|
|
|
local co_create, co_status, kresume, kyield = coroutine.create, coroutine.status, coroutine.resume, coroutine.yield
|
|
|
|
|
local huge = math.huge
|
|
|
|
|
local format = string.format
|
|
|
|
|
local pack, unpack = table.pack, table.unpack
|
|
|
|
|
|
|
|
|
|
local big = math.maxinteger or huge --some large amount to read at once in readfileK
|
|
|
|
|
local big = math.maxinteger or huge --some large amount to read at once in kreadfile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--completely disable the environment, ensuring the kernel only uses local variables, to prevent userspace
|
|
|
|
@ -26,7 +26,7 @@ _ENV = setmetatable({}, {
|
|
|
|
|
__newindex = function(self, key) return error(format('assigned global %q', key), 2) end,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
local function readfileK(path, fs)
|
|
|
|
|
local function kreadfile(path, fs)
|
|
|
|
|
fs = fs or bootfs
|
|
|
|
|
local text, chunk, fd, err = '', nil, invoke(fs, 'open', path)
|
|
|
|
|
if not fd then
|
|
|
|
@ -47,15 +47,38 @@ end
|
|
|
|
|
|
|
|
|
|
--[=====[ process internals ]=====]--
|
|
|
|
|
local processes = {} --key is thread, value is a table of process state (ie env)
|
|
|
|
|
local fillEnv --function(process:thread, env:table):() -- given a process and an empty environment, fill it in before the process runs
|
|
|
|
|
local runnable = {} --key is thread, value is pack(...)ed args to resume it with
|
|
|
|
|
local schedDeadline = {} --key is thread, value is absolute deadline of uptime()
|
|
|
|
|
local schedSignal = {} --key is thread, value is `true` (may allow specific signal names in the future)
|
|
|
|
|
local fillEnv --function(proc:thread, env:table):() -- given a process and an empty environment, fill it in before the process runs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--[=====[ process environments ]=====]--
|
|
|
|
|
--<https://ocdoc.cil.li/tutorial:custom_oses#what_s_available>
|
|
|
|
|
|
|
|
|
|
-- _G (through envBase) is used as the base for process environments, so clear it of anything process-specific
|
|
|
|
|
envBase._OSVERSION = 'kitn 0.0.0' --prefer [openloader, openos] "$name $ver" over plan9k "$name/$ver"
|
|
|
|
|
envBase._G, envBase.load, envBase.coroutine.resume, envBase.coroutine.yield, envBase.computer.pullSignal = nil
|
|
|
|
|
envBase._OSVERSION = 'kitn 0.1.0' --prefer [openloader, openos] "$name $ver" over plan9k "$name/$ver"
|
|
|
|
|
envBase._G, envBase.load = nil
|
|
|
|
|
envBase.coroutine.resume = function(co, ...)
|
|
|
|
|
checkArg(1, co, 'thread')
|
|
|
|
|
local results = pack(co_resume(co, ...)) --ok, os_reason, ...
|
|
|
|
|
if results[1] and results[2] then
|
|
|
|
|
return kyield(unpack(results, 2, results.n))
|
|
|
|
|
else
|
|
|
|
|
return unpack(results, 1, results.n)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
envBase.coroutine.yield = function(...) return kyield(false, ...) end
|
|
|
|
|
envBase.os.sleep = function(n)
|
|
|
|
|
checkArg(1, n, 'number')
|
|
|
|
|
return kyield('deadline', n)
|
|
|
|
|
end
|
|
|
|
|
envBase.computer.pullSignal = function(timeout)
|
|
|
|
|
checkArg(1, timeout, 'number', 'nil')
|
|
|
|
|
return kyield('signal', timeout)
|
|
|
|
|
end
|
|
|
|
|
envBase.kitn = {} --our specific goodies
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--environment sandboxing uses mirrors: tables that provide a read-only "reflection" of what's on the other side
|
|
|
|
|
local mirror --function(target:table[, mirror:table]):table -- create a mirror (or make a table into one) that reflects `target`
|
|
|
|
@ -78,7 +101,7 @@ local mirrormt = {
|
|
|
|
|
end,
|
|
|
|
|
__pairs = function(self)
|
|
|
|
|
--reuse a single function with the mirror as the control variable, instead of creating a new function with
|
|
|
|
|
--upvalues every time, because that seems vaguely less memory-intensive (TODO benchmark that)
|
|
|
|
|
--upvalues every time, for vague feelings of less memory usage (TODO benchmark that)
|
|
|
|
|
return mirrorNext, self
|
|
|
|
|
end,
|
|
|
|
|
--mirrors allow assignment but don't do anything with it
|
|
|
|
@ -91,65 +114,65 @@ function mirror(target, mirror)
|
|
|
|
|
return mirror
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function fillEnv(process, env)
|
|
|
|
|
--<https://ocdoc.cil.li/tutorial:custom_oses#what_s_available>
|
|
|
|
|
mirror(envBase, env)
|
|
|
|
|
function fillEnv(proc, uenv)
|
|
|
|
|
mirror(envBase, uenv)
|
|
|
|
|
uenv._G = uenv
|
|
|
|
|
|
|
|
|
|
local function env_load(chunk, name, mode, fenv)
|
|
|
|
|
local function uload(chunk, name, mode, env)
|
|
|
|
|
checkArg(1, chunk, 'function', 'string')
|
|
|
|
|
checkArg(2, name, 'string', 'nil')
|
|
|
|
|
checkArg(3, mode, 'string', 'nil')
|
|
|
|
|
checkArg(4, fenv, 'table', 'nil')
|
|
|
|
|
return loadK(chunk, name, mode, fenv or env)
|
|
|
|
|
checkArg(4, env, 'table', 'nil')
|
|
|
|
|
return kload(chunk, name, mode, env or uenv)
|
|
|
|
|
end
|
|
|
|
|
local function env_loadfile(path, mode, fenv)
|
|
|
|
|
local function uloadfile(path, mode, env)
|
|
|
|
|
checkArg(1, path, 'string')
|
|
|
|
|
checkArg(2, mode, 'string', 'nil')
|
|
|
|
|
checkArg(3, newenv, 'table', 'nil')
|
|
|
|
|
checkArg(3, env, 'table', 'nil')
|
|
|
|
|
|
|
|
|
|
local code, err = readfileK(path)
|
|
|
|
|
local code, err = kreadfile(path)
|
|
|
|
|
if not code then return nil, err end
|
|
|
|
|
|
|
|
|
|
code, err = env_load(code, '@' .. path, mode, fenv)
|
|
|
|
|
code, err = uload(code, '@' .. path, mode, env)
|
|
|
|
|
return code, err
|
|
|
|
|
end
|
|
|
|
|
local function env_dofile(path, ...)
|
|
|
|
|
local function udofile(path, ...)
|
|
|
|
|
checkArg(1, path, 'string')
|
|
|
|
|
local fn = assert(env_loadfile(path))
|
|
|
|
|
local fn = assert(uloadfile(path))
|
|
|
|
|
return fn(...)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
env.load, env.loadfile, env.dofile = env_load, env_loadfile, env_dofile
|
|
|
|
|
env.coroutine = mirror(envBase.coroutine, {
|
|
|
|
|
resume = function(co, ...)
|
|
|
|
|
local results = pack(co_resume(co, ...)) --ok, os_reason, ...
|
|
|
|
|
if results[1] and results[2] then
|
|
|
|
|
return co_yieldK(unpack(results, 2, results.n))
|
|
|
|
|
else
|
|
|
|
|
return unpack(results, 1, results.n)
|
|
|
|
|
end
|
|
|
|
|
end,
|
|
|
|
|
yield = function(...) return co_yieldK(false, ...) end,
|
|
|
|
|
})
|
|
|
|
|
env.os = mirror(envBase.os, {
|
|
|
|
|
sleep = function(n)
|
|
|
|
|
checkArg(1, n, 'number')
|
|
|
|
|
return co_yieldK('deadline', n)
|
|
|
|
|
end,
|
|
|
|
|
})
|
|
|
|
|
env.computer = mirror(envBase.computer, {
|
|
|
|
|
pullSignal = function(timeout)
|
|
|
|
|
checkArg(1, timeout, 'number', 'nil')
|
|
|
|
|
return co_yieldK('signal', timeout)
|
|
|
|
|
end,
|
|
|
|
|
})
|
|
|
|
|
end
|
|
|
|
|
uenv.load, uenv.loadfile, uenv.dofile = uload, uloadfile, udofile
|
|
|
|
|
|
|
|
|
|
--env will automatically mirror envBase.kitn here
|
|
|
|
|
local ukitn = uenv.kitn
|
|
|
|
|
|
|
|
|
|
--[=====[ scheduler ]=====]--
|
|
|
|
|
local runnable = {} --key is thread, value is pack(...)ed args to resume it with
|
|
|
|
|
local schedDeadline = {} --key is thread, value is absolute deadline of uptime()
|
|
|
|
|
local schedSignal = {} --key is thread, value is `true` (may allow specific signal names in the future)
|
|
|
|
|
function ukitn.createThread(fn, ...)
|
|
|
|
|
checkArg(1, fn, 'function')
|
|
|
|
|
local newThread = co_create(fn)
|
|
|
|
|
processes[newThread] = { thread = true, parent = proc } --mark as thread of same process
|
|
|
|
|
runnable[newThread] = pack(...)
|
|
|
|
|
return newThread
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function ukitn.createProcess(path, ...)
|
|
|
|
|
checkArg(1, path, 'string')
|
|
|
|
|
local env = mirror(envBase)
|
|
|
|
|
local fn, err = uloadfile(path, nil, env)
|
|
|
|
|
if not fn then return nil, err end
|
|
|
|
|
|
|
|
|
|
local newProc = co_create(fn)
|
|
|
|
|
processes[newProc] = { env = env, parent = proc, path = path }
|
|
|
|
|
runnable[newProc] = pack(...)
|
|
|
|
|
return newProc
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function ukitn.running()
|
|
|
|
|
--TODO return name, child processes, child threads, ...
|
|
|
|
|
--TODO return information for the current thread within the process too
|
|
|
|
|
return proc, nil
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--[=====[ init, pid 1 ]=====]--
|
|
|
|
@ -157,11 +180,11 @@ do
|
|
|
|
|
local initEnv = {}
|
|
|
|
|
--since init is intended to be configured per-computer, it lives in /etc
|
|
|
|
|
local initPath = '/etc/init.lua'
|
|
|
|
|
local initCode = assert(readfileK(initPath))
|
|
|
|
|
local initFn = assert(loadK(initCode, '@' .. initPath, 'bt', initEnv))
|
|
|
|
|
fillEnv(initFn, initEnv)
|
|
|
|
|
local initCode = assert(kreadfile(initPath))
|
|
|
|
|
local initFn = assert(kload(initCode, '@' .. initPath, 'bt', initEnv))
|
|
|
|
|
local initThread = co_create(initFn)
|
|
|
|
|
processes[initThread] = { env = initEnv }
|
|
|
|
|
fillEnv(initThread, initEnv)
|
|
|
|
|
processes[initThread] = { env = initEnv, source = initPath }
|
|
|
|
|
runnable[initThread] = { initPath, n = 1 }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
@ -190,7 +213,7 @@ local function postResume(co, ok, reason, ...)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--logic after pullSignalK returns, in its own function to use varargs instead of table.pack
|
|
|
|
|
--logic after kpullSignal returns, in its own function to use varargs instead of table.pack
|
|
|
|
|
local function postPull(signal, ...)
|
|
|
|
|
local args = pack(nil)
|
|
|
|
|
for proc, deadline in pairs(schedDeadline) do
|
|
|
|
@ -211,13 +234,13 @@ local function postPull(signal, ...)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--goto instead of while-true for vague performance
|
|
|
|
|
--goto instead of while-true for vague feeling of performance (TODO benchmark this)
|
|
|
|
|
::tickScheduler::
|
|
|
|
|
|
|
|
|
|
--clear the run queue
|
|
|
|
|
for proc, args in pairs(runnable) do
|
|
|
|
|
runnable[proc] = nil --the process is no longer runnable, unless it reschedules itself
|
|
|
|
|
postResume(proc, co_resumeK(proc, unpack(args, 1, args.n)))
|
|
|
|
|
postResume(proc, kresume(proc, unpack(args, 1, args.n)))
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local deadline = nil
|
|
|
|
@ -249,7 +272,7 @@ end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--`deadline` is set, so at least one process is waiting for a deadline or signal
|
|
|
|
|
postPull(pullSignalK(deadline - uptime()))
|
|
|
|
|
postPull(kpullSignal(deadline - uptime()))
|
|
|
|
|
|
|
|
|
|
--then do it again
|
|
|
|
|
goto tickScheduler
|
|
|
|
|