Experimenting with moving all of the OS out of the kernel, i.e. including stuff like the fileystem library. The bad: had to increase minimum memory module size quite a bit to make that work. The good: allows ultimate freedom to modify the OS without "external" changes.

Added an 'install' script to allow copying OpenOS to attached drives where it can then be edited.
Made cp.lua capable of recursive copying.
Added computer API entry to get and set the address of the preferred boot device (where kernel looks for init.lua before falling back to ROM).
Fixed NPE in sound cleanup.
Moved process module out of kernel into ROM.
Removed debug method i forgot to remove way back in io...
Removed deprecated methods.
Making active GC run only every so often, not on every resume.
This commit is contained in:
Florian Nücke 2014-04-12 20:58:39 +02:00
parent ceae60bfae
commit a6f36ef73c
15 changed files with 406 additions and 199 deletions

View File

@ -27,21 +27,6 @@ end
-------------------------------------------------------------------------------
local running = setmetatable({}, {__mode="k"})
local function findProcess(co)
co = co or coroutine.running()
for _, process in pairs(running) do
for _, instance in pairs(process.instances) do
if instance == co then
return process
end
end
end
end
-------------------------------------------------------------------------------
local function spcall(...)
local result = table.pack(pcall(...))
if not result[1] then
@ -71,7 +56,6 @@ sandbox = {
if not system.allowBytecode() then
mode = "t"
end
env = env or select(2, libprocess.running())
return load(ld, source, mode, env or sandbox)
end,
loadfile = nil, -- in boot/*_base.lua
@ -100,11 +84,7 @@ sandbox = {
end,
coroutine = {
create = function(f)
local co = coroutine.create(f)
table.insert(findProcess().instances, co)
return co
end,
create = coroutine.create,
resume = function(co, ...) -- custom resume part for bubbling sysyields
checkArg(1, co, "thread")
local args = table.pack(...)
@ -194,8 +174,8 @@ sandbox = {
pi = math.pi,
pow = math.pow,
rad = math.rad,
random = function(low, high)
return spcall(math.random, low, high)
random = function(...)
return spcall(math.random, ...)
end,
randomseed = function(seed)
spcall(math.randomseed, seed)
@ -288,7 +268,7 @@ libcomponent = {
doc = function(address, method)
checkArg(1, address, "string")
checkArg(2, method, "string")
local result, reason = component.doc(address, method)
local result, reason = spcall(component.doc, address, method)
if not result and reason then
error(reason, 2)
end
@ -297,7 +277,7 @@ libcomponent = {
invoke = function(address, method, ...)
checkArg(1, address, "string")
checkArg(2, method, "string")
local methods, reason = component.methods(address)
local methods, reason = spcall(component.methods, address)
if not methods then
return nil, reason
end
@ -310,7 +290,7 @@ libcomponent = {
end,
list = function(filter)
checkArg(1, filter, "string", "nil")
local list = component.list(filter)
local list = spcall(component.list, filter)
local key = nil
return function()
key = next(list, key)
@ -321,12 +301,12 @@ libcomponent = {
end,
proxy = function(address)
checkArg(1, address, "string")
local type, reason = component.type(address)
local type, reason = spcall(component.type, address)
if not type then
return nil, reason
end
local proxy = {address = address, type = type}
local methods, reason = component.methods(address)
local methods, reason = spcall(component.methods, address)
if not methods then
return nil, reason
end
@ -340,6 +320,7 @@ libcomponent = {
return component.type(address)
end
}
sandbox.component = libcomponent
local libcomputer = {
isRobot = computer.isRobot,
@ -352,6 +333,11 @@ local libcomputer = {
energy = computer.energy,
maxEnergy = computer.maxEnergy,
getBootAddress = computer.getBootAddress,
setBootAddress = function(address)
return spcall(computer.setBootAddress, address)
end,
users = computer.users,
addUser = function(name)
return spcall(computer.addUser, name)
@ -377,51 +363,7 @@ local libcomputer = {
until computer.uptime() >= deadline
end
}
libprocess = {
load = function(path, env, init, name)
checkArg(1, path, "string")
checkArg(2, env, "table", "nil")
checkArg(3, init, "function", "nil")
checkArg(4, name, "string", "nil")
local process = findProcess()
if process then
env = env or process.env
end
env = setmetatable({}, {__index=env or sandbox})
local code, reason = sandbox.loadfile(path, "t", env)
if not code then
return nil, reason
end
local thread = coroutine.create(function(...)
if init then
init()
end
return code(...)
end)
running[thread] = {
path = path,
command = name,
env = env,
parent = process,
instances = setmetatable({thread}, {__mode="v"})
}
return thread
end,
running = function(level)
level = level or 1
local process = findProcess()
while level > 1 and process do
process = process.parent
level = level - 1
end
if process then
return process.path, process.env, process.command
end
end
}
sandbox.computer = libcomputer
local libunicode = {
char = function(...)
@ -446,91 +388,48 @@ local libunicode = {
return spcall(unicode.upper, s)
end
}
sandbox.unicode = libunicode
-------------------------------------------------------------------------------
local function bootstrap()
-- Minimalistic hard-coded pure async proxy for our ROM.
local rom = {}
function rom.invoke(method, ...)
return invoke(true, computer.romAddress(), method, ...)
end
function rom.open(file) return rom.invoke("open", file) end
function rom.read(handle) return rom.invoke("read", handle, math.huge) end
function rom.close(handle) return rom.invoke("close", handle) end
function rom.inits(file) return ipairs(rom.invoke("list", "boot")) end
function rom.isDirectory(path) return rom.invoke("isDirectory", path) end
-- Custom low-level loadfile/dofile implementation reading from our ROM.
local function loadfile(file)
local handle, reason = rom.open(file)
local function tryLoadFrom(address)
function boot_invoke(method, ...)
local result = table.pack(pcall(invoke, true, address, method, ...))
if not result[1] then
return nil, result[2]
else
return table.unpack(result, 2, result.n)
end
end
local handle, reason = boot_invoke("open", "/init.lua")
if not handle then
error(reason)
return nil, reason
end
local buffer = ""
repeat
local data, reason = rom.read(handle)
local data, reason = boot_invoke("read", handle, math.huge)
if not data and reason then
error(reason)
return nil, reason
end
buffer = buffer .. (data or "")
until not data
rom.close(handle)
return load(buffer, "=" .. file, "t", sandbox)
boot_invoke("close", handle)
return load(buffer, "=init", "t", sandbox)
end
local function dofile(file)
local program, reason = loadfile(file)
if program then
local result = table.pack(pcall(program))
if result[1] then
return table.unpack(result, 2, result.n)
else
error(result[2])
end
else
error(reason)
end
local init, reason
if computer.getBootAddress() then
init, reason = tryLoadFrom(computer.getBootAddress())
end
if not init then
computer.setBootAddress()
init, reason = tryLoadFrom(computer.romAddress())
end
if not init then
error(reason)
end
-- Load file system related libraries we need to load other stuff moree
-- comfortably. This is basically wrapper stuff for the file streams
-- provided by the filesystem components.
local package = dofile("/lib/package.lua")
-- Initialize the package module with some of our own APIs.
package.preload["buffer"] = loadfile("/lib/buffer.lua")
package.preload["component"] = function() return libcomponent end
package.preload["computer"] = function() return libcomputer end
package.preload["filesystem"] = loadfile("/lib/filesystem.lua")
package.preload["io"] = loadfile("/lib/io.lua")
package.preload["process"] = function() return libprocess end
package.preload["unicode"] = function() return libunicode end
-- Inject the package and io modules into the global namespace, as in Lua.
sandbox.package = package
sandbox.io = sandbox.require("io")
-- Mount the ROM and temporary file systems to allow working on the file
-- system module from this point on.
sandbox.require("filesystem").mount(computer.romAddress(), "/")
if computer.tmpAddress() then
sandbox.require("filesystem").mount(computer.tmpAddress(), "/tmp")
end
-- Run library startup scripts. These mostly initialize event handlers.
local scripts = {}
for _, file in rom.inits() do
local path = "boot/" .. file
if not rom.isDirectory(path) then
table.insert(scripts, path)
end
end
table.sort(scripts)
for i = 1, #scripts do
dofile(scripts[i])
end
return coroutine.create(function() dofile("/init.lua") end), {n=0}
return coroutine.create(init), {n=0}
end
local function wrapUserdata(values)
@ -582,18 +481,12 @@ end
-------------------------------------------------------------------------------
local function main()
-- Make all calls in the bootstrapper direct to speed up booting.
local realInvoke = invoke
invoke = function(_, ...) return realInvoke(true, ...) end
local co, args = bootstrap()
-- Step out of the fast lane, all the basic stuff should now be loaded.
invoke = realInvoke
-- Yield once to get a memory baseline.
coroutine.yield()
-- After memory footprint to avoid init.lua bumping the baseline.
local co, args = bootstrap()
while true do
deadline = computer.realTime() + system.timeout()
debug.sethook(co, checkDeadline, "", hookInterval)

View File

@ -13,11 +13,43 @@ local to = shell.resolve(args[2])
if fs.isDirectory(to) then
to = to .. "/" .. fs.name(from)
end
if fs.exists(to) and not options.f then
io.stderr:write("target file exists")
return
local function status(from, to)
if options.v then
print(from .. " -> " .. to)
end
end
local result, reason = fs.copy(from, to)
local result, reason
local function recurse(fromPath, toPath)
status(fromPath, toPath)
if fs.isDirectory(fromPath) then
if fs.exists(toPath) and not fs.isDirectory(toPath) then
if not options.f then
return nil, "target file exists"
end
fs.remove(toPath)
end
fs.makeDirectory(toPath)
for file in fs.list(fromPath) do
local result, reason = recurse(fs.concat(fromPath, file), fs.concat(toPath, file))
if not result then
return nil, reason
end
end
return true
else
if fs.exists(toPath) then
if fs.isDirectory(toPath) or not options.f then
return nil, "target file exists"
end
fs.remove(toPath)
end
return fs.copy(fromPath, toPath)
end
end
result, reason = recurse(from, to)
if not result then
io.stderr:write(reason)
error(reason)
end

View File

@ -0,0 +1,60 @@
local component = require("component")
local computer = require("computer")
local event = require("event")
local unicode = require("unicode")
local candidates = {}
for address in component.list("filesystem") do
local dev = component.proxy(address)
if not dev.isReadOnly() and dev.address ~= computer.tmpAddress() then
table.insert(candidates, dev)
end
end
if #candidates == 0 then
print("No writable disks found, aborting.")
return
end
for i = 1, #candidates do
print(i .. ") " .. candidates[i].address)
end
print("To select the device to install to, please enter a number between 1 and " .. #candidates .. ".")
print("Press 'q' to cancel the installation.")
local choice
repeat
local _, address, char, code = event.pull("key")
if component.isPrimary(address) then
local value = unicode.char(char)
if value == "q" then
return
end
local number = tonumber(value)
if number and number > 0 and number <= #candidates then
choice = number
else
end
end
until choice
choice = candidates[choice]
print("Installing OpenOS to device " .. choice.address)
local rom = "/mnt/" .. computer.romAddress():sub(1, 3) .. "/"
local mnt = "/mnt/" .. choice.address:sub(1, 3) .. "/"
local function install(what, path)
print("Installing " .. what .. "...")
local result, reason = os.execute("cp -vf " .. rom .. path .. " " .. mnt)
if not result then
error(reason, 0)
end
end
install("programs", "bin")
install("boot scripts", "boot")
install("libraries", "lib")
install("additional files", "usr")
install("startup script", "init.lua")
computer.setBootAddress(choice.address)
print("All done! Rebooting from device now...")
computer.shutdown(true)

View File

@ -1,7 +1,7 @@
local component = require("component")
local package = require("package")
local term = require("term")
local text = require("text")
local serialization = require("serialization")
local function optrequire(...)
local success, module = pcall(require, ...)
@ -49,7 +49,7 @@ while term.isAvailable() do
io.stderr:write(tostring(result[2]) .. "\n")
else
for i = 2, result.n do
term.write(text.serialize(result[i], true) .. "\t", true)
term.write(serialization.serialize(result[i], true) .. "\t", true)
end
if term.getCursor() > 1 then
term.write("\n")

View File

@ -120,10 +120,11 @@ if #args == 0 and (io.input() == io.stdin or options.i) and not options.c then
return
elseif command ~= "" then
local result, reason = execute(command)
if term.getCursor() > 1 then
term.write("\n")
end
if not result then
io.stderr:write((tostring(reason) or "unknown error").. "\n")
elseif term.getCursor() > 1 then
term.write("\n")
end
end
end
@ -132,7 +133,7 @@ else
-- execute command.
local result = table.pack(execute(table.unpack(args)))
if not result[1] then
error(result[2])
error(result[2], 0)
end
return table.unpack(result, 2)
end

View File

@ -1,13 +1,147 @@
local component = require("component")
do
local component = component
local computer = computer
local unicode = unicode
-- Low level dofile implementation to read filesystem libraries.
local rom = {}
function rom.invoke(method, ...)
return component.invoke(computer.romAddress(), method, ...)
end
function rom.open(file) return rom.invoke("open", file) end
function rom.read(handle) return rom.invoke("read", handle, math.huge) end
function rom.close(handle) return rom.invoke("close", handle) end
function rom.inits(file) return ipairs(rom.invoke("list", "boot")) end
function rom.isDirectory(path) return rom.invoke("isDirectory", path) end
-- Report boot progress if possible.
local gpu, screen = component.list("gpu")(), component.list("screen")()
local w, h
if gpu and screen then
component.invoke(gpu, "bind", screen)
w, h = component.invoke(gpu, "getResolution")
component.invoke(gpu, "setResolution", w, h)
component.invoke(gpu, "setBackground", 0x000000)
component.invoke(gpu, "setForeground", 0xFFFFFF)
component.invoke(gpu, "fill", 1, 1, w, h, " ")
end
local y = 1
local function status(msg)
if gpu and screen then
component.invoke(gpu, "set", 1, y, msg)
if y == h then
component.invoke(gpu, "copy", 1, 2, w, h - 1, 0, -1)
component.invoke(gpu, "fill", 1, h, w, 1, " ")
else
y = y + 1
end
end
end
status("Booting " .. _OSVERSION .. "...")
-- Custom low-level loadfile/dofile implementation reading from our ROM.
local function loadfile(file)
status("> " .. file)
local handle, reason = rom.open(file)
if not handle then
error(reason)
end
local buffer = ""
repeat
local data, reason = rom.read(handle)
if not data and reason then
error(reason)
end
buffer = buffer .. (data or "")
until not data
rom.close(handle)
return load(buffer, "=" .. file)
end
local function dofile(file)
local program, reason = loadfile(file)
if program then
local result = table.pack(pcall(program))
if result[1] then
return table.unpack(result, 2, result.n)
else
error(result[2])
end
else
error(reason)
end
end
status("Initializing package management...")
-- Load file system related libraries we need to load other stuff moree
-- comfortably. This is basically wrapper stuff for the file streams
-- provided by the filesystem components.
local package = dofile("/lib/package.lua")
do
-- Unclutter global namespace now that we have the package module.
_G.component = nil
_G.computer = nil
_G.process = nil
_G.unicode = nil
-- Initialize the package module with some of our own APIs.
package.preload["buffer"] = loadfile("/lib/buffer.lua")
package.preload["component"] = function() return component end
package.preload["computer"] = function() return computer end
package.preload["filesystem"] = loadfile("/lib/filesystem.lua")
package.preload["io"] = loadfile("/lib/io.lua")
package.preload["unicode"] = function() return unicode end
-- Inject the package and io modules into the global namespace, as in Lua.
_G.package = package
_G.io = require("io")
end
status("Initializing file system...")
-- Mount the ROM and temporary file systems to allow working on the file
-- system module from this point on.
local filesystem = require("filesystem")
filesystem.mount(computer.getBootAddress() or computer.romAddress(), "/")
if computer.tmpAddress() then
filesystem.mount(computer.tmpAddress(), "/tmp")
end
status("Running boot scripts...")
-- Run library startup scripts. These mostly initialize event handlers.
local scripts = {}
for _, file in rom.inits() do
local path = "boot/" .. file
if not rom.isDirectory(path) then
table.insert(scripts, path)
end
end
table.sort(scripts)
for i = 1, #scripts do
dofile(scripts[i])
end
-- Initialize process module.
require("process").install("/init.lua", "init")
status("Initializing components...")
for c, t in component.list() do
computer.pushSignal("component_added", c, t)
end
os.sleep(0.5) -- Allow signal processing by libraries.
computer.pushSignal("init") -- so libs know components are initialized.
status("Starting shell...")
end
local computer = require("computer")
local event = require("event")
for c, t in component.list() do
computer.pushSignal("component_added", c, t)
end
os.sleep(0.5) -- Allow signal processing by libraries.
computer.pushSignal("init") -- so libs know components are initialized.
while true do
require("term").clear()
io.write(_OSVERSION .. " (" .. math.floor(computer.totalMemory() / 1024) .. "k RAM)\n")

View File

@ -2,7 +2,7 @@ local io, file = {}, {}
local input, output
local programs = setmetatable({}, {__mode="k"}) -- maps program envs to i/o
function io.progs() return programs end
local function findOverride(filter)
local override
pcall(function()

View File

@ -0,0 +1,85 @@
local process = {}
-------------------------------------------------------------------------------
local running = setmetatable({}, {__mode="k"})
local function findProcess(co)
co = co or coroutine.running()
for _, process in pairs(running) do
for _, instance in pairs(process.instances) do
if instance == co then
return process
end
end
end
end
-------------------------------------------------------------------------------
function process.load(path, env, init, name)
checkArg(1, path, "string")
checkArg(2, env, "table", "nil")
checkArg(3, init, "function", "nil")
checkArg(4, name, "string", "nil")
local process = findProcess()
if process then
env = env or process.env
end
env = setmetatable({}, {__index=env or _G})
local code, reason = loadfile(path, "t", env)
if not code then
return nil, reason
end
local thread = coroutine.create(function(...)
if init then
init()
end
return code(...)
end)
running[thread] = {
path = path,
command = name,
env = env,
parent = process,
instances = setmetatable({thread}, {__mode="v"})
}
return thread
end
function process.running(level)
level = level or 1
local process = findProcess()
while level > 1 and process do
process = process.parent
level = level - 1
end
if process then
return process.path, process.env, process.command
end
end
function process.install(path, name)
local coroutine_create = coroutine.create
_G.coroutine.create = function(f)
local co = coroutine_create(f)
table.insert(findProcess().instances, co)
return co
end
local load = load
_G.load = function(ld, source, mode, env)
env = env or select(2, process.running())
return load(ld, source, mode, env)
end
local thread = coroutine.running()
running[thread] = {
path = path,
command = name,
env = _ENV,
instances = setmetatable({thread}, {__mode="v"})
}
end
return process

View File

@ -1,6 +1,4 @@
local event = require("event")
local fs = require("filesystem")
local process = require("process")
local text = require("text")
local unicode = require("unicode")
@ -165,10 +163,6 @@ function shell.parse(...)
return args, options
end
function shell.running(level) -- deprecated
return require("process").running(level)
end
-------------------------------------------------------------------------------
return shell

View File

@ -17,7 +17,6 @@ end
function text.padRight(value, length)
checkArg(1, value, "string", "nil")
checkArg(2, length, "number")
local unicode = require("unicode")
if not value or unicode.len(value) == 0 then
return string.rep(" ", length)
else
@ -28,7 +27,6 @@ end
function text.padLeft(value, length)
checkArg(1, value, "string", "nil")
checkArg(2, length, "number")
local unicode = require("unicode")
if not value or unicode.len(value) == 0 then
return string.rep(" ", length)
else
@ -99,14 +97,6 @@ function text.tokenize(value)
return tokens
end
function text.serialize(value, pretty) -- deprecated, use serialization module
return require("serialization").serialize(value, pretty)
end
function text.unserialize(data) -- deprecated, use serialization module
return require("serialization").unserialize(data)
end
-------------------------------------------------------------------------------
return text

View File

@ -110,11 +110,11 @@ opencomputers {
# there are six levels of RAM, they still fall into the three tiers of
# items (level 1, 2 = tier 1, level 3, 4 = tier 2, level 5, 6 = tier 3).
ramSizes: [
64
96
128
192
256
384
512
768
1024
]

View File

@ -38,17 +38,11 @@ class Settings(config: Config) {
val startupDelay = config.getDouble("computer.startupDelay") max 0.05
val activeGC = config.getBoolean("computer.activeGC")
val ramSizes = Array(config.getIntList("computer.ramSizes"): _*) match {
case Array(tier1, tier2, tier3) =>
// For compatibility with older config files.
Array(tier1: Int, (tier1: Int) * 3 / 2, tier2: Int, tier3: Int, tier3 * 2: Int, tier3 * 4: Int)
case Array(tier1, tier3, tier4, tier5, tier6) =>
// For compatibility with older config files.
Array(tier1: Int, (tier1: Int) * 3 / 2, tier3: Int, tier4: Int, tier5: Int, tier6: Int)
case Array(tier1, tier2, tier3, tier4, tier5, tier6) =>
Array(tier1: Int, tier2: Int, tier3: Int, tier4: Int, tier5: Int, tier6: Int)
case _ =>
OpenComputers.log.warning("Bad number of RAM sizes, ignoring.")
Array(64, 96, 128, 256, 512, 1024)
Array(192, 256, 384, 512, 768, 1024)
}
val ramScaleFor64Bit = config.getDouble("computer.ramScaleFor64Bit") max 1
val cpuComponentSupport = Array(config.getIntList("computer.cpuComponentCount"): _*) match {

View File

@ -167,8 +167,10 @@ object Sound {
}
def stop() {
soundSystem.stop(source)
soundSystem.removeSource(source)
if (soundSystem != null) {
soundSystem.stop(source)
soundSystem.removeSource(source)
}
}
}

View File

@ -17,6 +17,8 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu
private[machine] val ramScale = if (LuaStateFactory.is64Bit) Settings.get.ramScaleFor64Bit else 1.0
private[machine] var bootAddress = ""
private val persistence = new PersistenceAPI(this)
private val apis = Array(
@ -28,6 +30,8 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu
new UnicodeAPI(this),
new UserdataAPI(this))
private var lastCollection = 0L
// ----------------------------------------------------------------------- //
override def name() = "Lua"
@ -73,9 +77,10 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu
// The kernel thread will always be at stack index one.
assert(lua.isThread(1))
if (Settings.get.activeGC) {
if (Settings.get.activeGC && (machine.worldTime - lastCollection) > 20) {
// Help out the GC a little. The emergency GC has a few limitations
// that will make it free less memory than doing a full step manually.
lastCollection = machine.worldTime
lua.gc(LuaState.GcAction.COLLECT, 0)
}

View File

@ -38,6 +38,23 @@ class ComputerAPI(owner: NativeLuaArchitecture) extends NativeLuaAPI(owner) {
})
lua.setField(-2, "address")
// Get/set address of boot device.
lua.pushScalaFunction(lua => {
owner.bootAddress match {
case "" => lua.pushNil()
case address => lua.pushString(address)
}
1
})
lua.setField(-2, "getBootAddress")
lua.pushScalaFunction(lua => {
if (lua.isNoneOrNil(1)) owner.bootAddress = ""
else owner.bootAddress = lua.checkString(1)
0
})
lua.setField(-2, "setBootAddress")
lua.pushScalaFunction(lua => {
// This is *very* unlikely, but still: avoid this getting larger than
// what we report as the total memory.