diff --git a/src/init.lua b/src/init.lua index a1c342a..d2829c0 100644 --- a/src/init.lua +++ b/src/init.lua @@ -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 ]=====]-- -- -- _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) - -- - 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