add apis for userspace to spawn new processes/threads

main
cinder 7 months ago
parent ca37976828
commit 3e5cfde820

@ -5,18 +5,18 @@ bootfs = bootfs or computer.getBootAddress()
--store all functions we need in locals so userspace mischief can't replace them --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`) --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 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 invoke = component.invoke
local pullSignalK, shutdown, uptime = computer.pullSignal, computer.shutdown, computer.uptime local kpullSignal, 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 co_create, co_status, kresume, kyield = coroutine.create, coroutine.status, coroutine.resume, coroutine.yield
local huge = math.huge local huge = math.huge
local format = string.format local format = string.format
local pack, unpack = table.pack, table.unpack 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 --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, __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 fs = fs or bootfs
local text, chunk, fd, err = '', nil, invoke(fs, 'open', path) local text, chunk, fd, err = '', nil, invoke(fs, 'open', path)
if not fd then if not fd then
@ -47,15 +47,38 @@ end
--[=====[ process internals ]=====]-- --[=====[ process internals ]=====]--
local processes = {} --key is thread, value is a table of process state (ie env) 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 ]=====]-- --[=====[ process environments ]=====]--
--<https://ocdoc.cil.li/tutorial:custom_oses#what_s_available> --<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 -- _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._OSVERSION = 'kitn 0.1.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._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 --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` 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, end,
__pairs = function(self) __pairs = function(self)
--reuse a single function with the mirror as the control variable, instead of creating a new function with --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 return mirrorNext, self
end, end,
--mirrors allow assignment but don't do anything with it --mirrors allow assignment but don't do anything with it
@ -91,65 +114,65 @@ function mirror(target, mirror)
return mirror return mirror
end end
function fillEnv(process, env) function fillEnv(proc, uenv)
--<https://ocdoc.cil.li/tutorial:custom_oses#what_s_available> mirror(envBase, uenv)
mirror(envBase, env) uenv._G = uenv
local function env_load(chunk, name, mode, fenv) local function uload(chunk, name, mode, env)
checkArg(1, chunk, 'function', 'string') checkArg(1, chunk, 'function', 'string')
checkArg(2, name, 'string', 'nil') checkArg(2, name, 'string', 'nil')
checkArg(3, mode, 'string', 'nil') checkArg(3, mode, 'string', 'nil')
checkArg(4, fenv, 'table', 'nil') checkArg(4, env, 'table', 'nil')
return loadK(chunk, name, mode, fenv or env) return kload(chunk, name, mode, env or uenv)
end end
local function env_loadfile(path, mode, fenv) local function uloadfile(path, mode, env)
checkArg(1, path, 'string') checkArg(1, path, 'string')
checkArg(2, mode, 'string', 'nil') 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 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 return code, err
end end
local function env_dofile(path, ...) local function udofile(path, ...)
checkArg(1, path, 'string') checkArg(1, path, 'string')
local fn = assert(env_loadfile(path)) local fn = assert(uloadfile(path))
return fn(...) return fn(...)
end end
env.load, env.loadfile, env.dofile = env_load, env_loadfile, env_dofile uenv.load, uenv.loadfile, uenv.dofile = uload, uloadfile, udofile
env.coroutine = mirror(envBase.coroutine, {
resume = function(co, ...) --env will automatically mirror envBase.kitn here
local results = pack(co_resume(co, ...)) --ok, os_reason, ... local ukitn = uenv.kitn
if results[1] and results[2] then
return co_yieldK(unpack(results, 2, results.n)) function ukitn.createThread(fn, ...)
else checkArg(1, fn, 'function')
return unpack(results, 1, results.n) local newThread = co_create(fn)
end processes[newThread] = { thread = true, parent = proc } --mark as thread of same process
end, runnable[newThread] = pack(...)
yield = function(...) return co_yieldK(false, ...) end, return newThread
})
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 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
--[=====[ scheduler ]=====]-- function ukitn.running()
local runnable = {} --key is thread, value is pack(...)ed args to resume it with --TODO return name, child processes, child threads, ...
local schedDeadline = {} --key is thread, value is absolute deadline of uptime() --TODO return information for the current thread within the process too
local schedSignal = {} --key is thread, value is `true` (may allow specific signal names in the future) return proc, nil
end
end
--[=====[ init, pid 1 ]=====]-- --[=====[ init, pid 1 ]=====]--
@ -157,11 +180,11 @@ do
local initEnv = {} local initEnv = {}
--since init is intended to be configured per-computer, it lives in /etc --since init is intended to be configured per-computer, it lives in /etc
local initPath = '/etc/init.lua' local initPath = '/etc/init.lua'
local initCode = assert(readfileK(initPath)) local initCode = assert(kreadfile(initPath))
local initFn = assert(loadK(initCode, '@' .. initPath, 'bt', initEnv)) local initFn = assert(kload(initCode, '@' .. initPath, 'bt', initEnv))
fillEnv(initFn, initEnv)
local initThread = co_create(initFn) local initThread = co_create(initFn)
processes[initThread] = { env = initEnv } fillEnv(initThread, initEnv)
processes[initThread] = { env = initEnv, source = initPath }
runnable[initThread] = { initPath, n = 1 } runnable[initThread] = { initPath, n = 1 }
end end
@ -190,7 +213,7 @@ local function postResume(co, ok, reason, ...)
end end
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 function postPull(signal, ...)
local args = pack(nil) local args = pack(nil)
for proc, deadline in pairs(schedDeadline) do for proc, deadline in pairs(schedDeadline) do
@ -211,13 +234,13 @@ local function postPull(signal, ...)
end end
--goto instead of while-true for vague performance --goto instead of while-true for vague feeling of performance (TODO benchmark this)
::tickScheduler:: ::tickScheduler::
--clear the run queue --clear the run queue
for proc, args in pairs(runnable) do for proc, args in pairs(runnable) do
runnable[proc] = nil --the process is no longer runnable, unless it reschedules itself 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 end
local deadline = nil local deadline = nil
@ -249,7 +272,7 @@ end
--`deadline` is set, so at least one process is waiting for a deadline or signal --`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 --then do it again
goto tickScheduler goto tickScheduler

Loading…
Cancel
Save